From 72b92ba69d97ad124a74c4382256da7596b45a3f Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Sat, 11 Oct 2025 19:40:18 +0300 Subject: [PATCH 01/49] chore: configures semantic release for develop Configures semantic release to trigger releases from the develop branch. Adds a badge to the README to display semantic release status. --- .releaserc.json | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.releaserc.json b/.releaserc.json index 3628b44..db40d8c 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -1,5 +1,5 @@ { - "branches": ["main"], + "branches": ["main", "next"], "plugins": [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", diff --git a/README.md b/README.md index 82c21c3..df419a5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ 🚀 Modern Next.js boilerplate with TypeScript, authentication, and development automation - CodeFactor Grade NPM Version License: Apache-2.0 + CodeFactor Grade NPM Version License: Apache-2.0 semantic-release: next.js [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=http%3A%2F%2Fgithub.com%2Fnitrokit%2Fnitrokit-nextjs&env=AUTH_SECRET,DATABASE_URL,GOOGLE_SITE_VERIFICATION,GOOGLE_ANALYTICS,YANDEX_VERIFICATION,EMAIL_PROVIDER,RESEND_API_KEY,RESEND_AUDIENCE_ID,RESEND_FROM_EMAIL,UPSTASH_REDIS_REST_URL&project-name=nitrokit&repository-name=nitrokit-nextjs&demo-title=Nitrokit&demo-description=%F0%9F%9A%80%20A%20modern%20and%20production-ready%20Next.js%20boilerplate.%20It%20provides%20a%20quick%20start%20with%20TypeScript%2C%20i18n%20support%2C%20and%20automated%20tooling.&demo-url=https%3A%2F%2Fpreview.nitrokit.tr&demo-image=https%3A%2F%2Fraw.githubusercontent.com%2Fnitrokit%2Fnitrokit-nextjs%2Frefs%2Fheads%2Fmain%2Fpublic%2Fscreenshots%2Fscreenshot-1.png) From 18c71be69d17c4ffd5a5ebd175a5ec663d5d10a7 Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Sat, 11 Oct 2025 20:01:46 +0300 Subject: [PATCH 02/49] chore: Cleans up console logs and updates CHANGELOG Removes unnecessary console logs. Updates the CHANGELOG file to reflect the project's initial release and subsequent features and fixes. --- CHANGELOG.md | 18 ++++++++---------- .../contact/components/contact-form.tsx | 1 - src/app/[locale]/(website)/page.tsx | 2 +- src/components/newsletter/form.tsx | 2 -- src/components/shared/cookie-consent.tsx | 8 ++------ src/hooks/useNextTheme.ts | 2 +- src/lib/security/rate-limit.ts | 4 ++-- 7 files changed, 14 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5966a6..66a8faf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,15 @@ # 1.0.0 (2025-10-11) - ### Bug Fixes -* Enhances code quality and fixes minor bugs ([02b69f1](https://github.com/nitrokit/nitrokit-nextjs/commit/02b69f11c1c5fad4c93f2f8117e0132407018cf1)) - +- Enhances code quality and fixes minor bugs ([02b69f1](https://github.com/nitrokit/nitrokit-nextjs/commit/02b69f11c1c5fad4c93f2f8117e0132407018cf1)) ### Features -* Adds 404 page with i18n support ([cd42e6d](https://github.com/nitrokit/nitrokit-nextjs/commit/cd42e6df3e6d21c0f3cc4be676b99205fdd8a807)) -* Adds Vercel badge to README ([aa5e217](https://github.com/nitrokit/nitrokit-nextjs/commit/aa5e217c930486d9a3b61cdb9c3ee58f01d3e2e1)) -* Configures dev environment with Docker ([de2fb06](https://github.com/nitrokit/nitrokit-nextjs/commit/de2fb06501a04a7df686dc0a4ce3ce9c0b21e961)) -* Enables theme customization ([a0f7ce0](https://github.com/nitrokit/nitrokit-nextjs/commit/a0f7ce071ed590dc8377bff1f3f6274b37950d37)) -* Enhances documentation with CLI info ([ecc4372](https://github.com/nitrokit/nitrokit-nextjs/commit/ecc4372f1934e1a0edfba68c262e86042774648f)) -* Enhances user session management ([32b5e01](https://github.com/nitrokit/nitrokit-nextjs/commit/32b5e0184f755d285b0c6fd8edc3f4910a497987)) -* first release of the application ([10dbd9a](https://github.com/nitrokit/nitrokit-nextjs/commit/10dbd9aa1da4edb60632f5b0facea5fd01454c33)) +- Adds 404 page with i18n support ([cd42e6d](https://github.com/nitrokit/nitrokit-nextjs/commit/cd42e6df3e6d21c0f3cc4be676b99205fdd8a807)) +- Adds Vercel badge to README ([aa5e217](https://github.com/nitrokit/nitrokit-nextjs/commit/aa5e217c930486d9a3b61cdb9c3ee58f01d3e2e1)) +- Configures dev environment with Docker ([de2fb06](https://github.com/nitrokit/nitrokit-nextjs/commit/de2fb06501a04a7df686dc0a4ce3ce9c0b21e961)) +- Enables theme customization ([a0f7ce0](https://github.com/nitrokit/nitrokit-nextjs/commit/a0f7ce071ed590dc8377bff1f3f6274b37950d37)) +- Enhances documentation with CLI info ([ecc4372](https://github.com/nitrokit/nitrokit-nextjs/commit/ecc4372f1934e1a0edfba68c262e86042774648f)) +- Enhances user session management ([32b5e01](https://github.com/nitrokit/nitrokit-nextjs/commit/32b5e0184f755d285b0c6fd8edc3f4910a497987)) +- first release of the application ([10dbd9a](https://github.com/nitrokit/nitrokit-nextjs/commit/10dbd9aa1da4edb60632f5b0facea5fd01454c33)) diff --git a/src/app/[locale]/(website)/contact/components/contact-form.tsx b/src/app/[locale]/(website)/contact/components/contact-form.tsx index 95fb4e2..219f268 100644 --- a/src/app/[locale]/(website)/contact/components/contact-form.tsx +++ b/src/app/[locale]/(website)/contact/components/contact-form.tsx @@ -55,7 +55,6 @@ export const ContactForm = () => { setFormStatus('idle'); try { const result: EmailServiceResult = await sendContactEmail(data); - console.log(result); if (!result.success) { setFormStatus('error'); toast.error(t('app.errors.general'), { diff --git a/src/app/[locale]/(website)/page.tsx b/src/app/[locale]/(website)/page.tsx index 6cf5bdf..32f0e10 100644 --- a/src/app/[locale]/(website)/page.tsx +++ b/src/app/[locale]/(website)/page.tsx @@ -4,7 +4,7 @@ // export default function Home() { // const { user, isLoading } = useUser(); -// console.log('User', user, isLoading); +// console.info('User', user, isLoading); // } import { BackgroundPatterns, LibraryLogos } from '@/components/layout'; diff --git a/src/components/newsletter/form.tsx b/src/components/newsletter/form.tsx index 859d7c6..ffbb67e 100644 --- a/src/components/newsletter/form.tsx +++ b/src/components/newsletter/form.tsx @@ -20,8 +20,6 @@ export function CompactNewsletter() { } }; - console.log(error); - return (

diff --git a/src/components/shared/cookie-consent.tsx b/src/components/shared/cookie-consent.tsx index 6ea395d..8ae2d53 100644 --- a/src/components/shared/cookie-consent.tsx +++ b/src/components/shared/cookie-consent.tsx @@ -139,13 +139,9 @@ export function CookieConsent() { } }; - const enableFunctional = () => { - console.log('Functional cookies enabled'); - }; + const enableFunctional = () => {}; - const disableFunctional = () => { - console.log('Functional cookies disabled'); - }; + const disableFunctional = () => {}; const acceptAll = () => { const allAccepted: CookiePreferences = { diff --git a/src/hooks/useNextTheme.ts b/src/hooks/useNextTheme.ts index 7f12833..c96b4fa 100644 --- a/src/hooks/useNextTheme.ts +++ b/src/hooks/useNextTheme.ts @@ -65,7 +65,7 @@ export function useNextTheme() { // if (response.ok) { // const result = await response.json(); // if (result.success && result.themeChanged) { - // console.log('💾 Theme preference saved to database'); + // console.info('💾 Theme preference saved to database'); // } // } } catch (error) { diff --git a/src/lib/security/rate-limit.ts b/src/lib/security/rate-limit.ts index 283d971..078c16f 100644 --- a/src/lib/security/rate-limit.ts +++ b/src/lib/security/rate-limit.ts @@ -11,7 +11,7 @@ try { }); } } catch (error) { - console.log(error); + console.error(error); redis = null; } @@ -53,7 +53,7 @@ export const smsRateLimit = redis export const fallbackRateLimit = { limit: (key: string) => { - console.log(key); + console.info(key); return { success: true, limit: 100, From 96ffb73df0e4d80089c16ba12b77dfd214752d3a Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Sat, 11 Oct 2025 20:03:23 +0300 Subject: [PATCH 03/49] chore: Extends protected branch list Updates the pre-commit hook to include an additional protected branch. This prevents direct commits to the 'next' branch, enforcing the established workflow. --- .husky/pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 73ba06e..8154141 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -10,7 +10,7 @@ echo '🚀 Checking if pre-commit validation is needed...' BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || git branch --show-current 2>/dev/null || echo "unknown") -PROTECTED_BRANCHES=("main") +PROTECTED_BRANCHES=("main" "next") DEVELOPMENT_BRANCHES=( "preview" "develop") RED='\033[0;31m' From 4aa9145f1ca248c78925b9a8a0c59d50196cf04f Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Sat, 11 Oct 2025 20:11:02 +0300 Subject: [PATCH 04/49] feat: Adds authentication and user context Introduces user authentication using NextAuth, Prisma, and JWT. Provides a user context for managing user data and authentication state throughout the application. Implements middleware for route protection and redirection to the login page if the user is not authenticated. --- src/app/[locale]/layout.tsx | 28 ++++++++------ src/app/api/auth/[...nextauth]/route.ts | 2 + src/auth.ts | 51 +++++++++++++++++++++++++ src/contexts/user-context.tsx | 38 ++++++++++++++++++ src/middleware.ts | 46 +++++++++++++++++++++- 5 files changed, 153 insertions(+), 12 deletions(-) create mode 100644 src/app/api/auth/[...nextauth]/route.ts create mode 100644 src/auth.ts create mode 100644 src/contexts/user-context.tsx diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 3d1e541..2809c10 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -9,6 +9,8 @@ import { getLangDir } from 'rtl-detect'; import { ThemeProvider } from '@/providers/theme-provider'; import { CookieConsent } from '@/components/shared'; import { TooltipProvider } from '@/components/ui'; +import { SessionProvider } from 'next-auth/react'; +import { UserProvider } from '@/contexts/user-context'; export async function generateMetadata(): Promise { return await generateSiteMetadata(); @@ -46,17 +48,21 @@ export default async function LocaleLayout({ children, params }: LocaleLayoutPro className={`${lexend.variable} ${montserrat.variable} font-[family-name:var(--font-lexend)] antialiased`} > - - {children} - - + + + + {children} + + + + diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..0aa1cd7 --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,2 @@ +import { handlers } from '@/auth'; +export const { GET, POST } = handlers; diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..68bd66a --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,51 @@ +import NextAuth from 'next-auth'; +import { PrismaAdapter } from '@auth/prisma-adapter'; +import { prisma } from './lib/prisma'; + +export const { handlers, auth, signIn, signOut } = NextAuth({ + adapter: PrismaAdapter(prisma), + providers: [], + pages: { + signIn: '/login', + signOut: '/logout', + error: '/error', + verifyRequest: '/verify-request', + newUser: '/register' + }, + session: { + strategy: 'jwt', + maxAge: 30 * 24 * 60 * 60, // 30 days + updateAge: 24 * 60 * 60 // 24 hours + }, + jwt: { + maxAge: 30 * 24 * 60 * 60 // 30 days + }, + secret: process.env.AUTH_SECRET, + debug: process.env.NODE_ENV === 'development', + useSecureCookies: process.env.NODE_ENV === 'production', + cookies: { + sessionToken: { + name: + process.env.NODE_ENV === 'production' + ? '__Secure-authjs.session-token' + : 'authjs.session-token', + options: { + httpOnly: true, + sameSite: 'lax', + path: '/', + secure: process.env.NODE_ENV === 'production' + } + } + }, + logger: { + error(code: any, ...message: any[]) { + console.error(code, message); + }, + warn(code: any, ...message: any[]) { + console.warn(code, message); + }, + debug(code: any, ...message: any[]) { + console.debug(code, message); + } + } +}); diff --git a/src/contexts/user-context.tsx b/src/contexts/user-context.tsx new file mode 100644 index 0000000..3e5bf8f --- /dev/null +++ b/src/contexts/user-context.tsx @@ -0,0 +1,38 @@ +'use client'; + +import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { useSession } from 'next-auth/react'; +import { UserContextType, UserData } from '@/types/user'; + +const UserContext = createContext(undefined); + +export const UserProvider = ({ children }: { children: ReactNode }) => { + const { data: session, status } = useSession(); + const [user, setUser] = useState(session?.user || null); + + useEffect(() => { + if (session) { + setUser(session.user as UserData); + } + }, [session]); + + const updateUser = (newUserData: UserData) => { + setUser(newUserData); + }; + + const contextValue: UserContextType = { + user, + updateUser, + isLoading: status === 'loading' + }; + + return {children}; +}; + +export const useUser = () => { + const context = useContext(UserContext); + if (!context) { + throw new Error('useUser must be used within a UserProvider'); + } + return context; +}; diff --git a/src/middleware.ts b/src/middleware.ts index 7c6a02b..6591e55 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,10 +1,54 @@ -import { NextRequest } from 'next/server'; +import { NextRequest, NextResponse } from 'next/server'; import createMiddleware from 'next-intl/middleware'; import { routing } from './lib/i18n/routing'; +import { PUBLIC_ROUTES } from './constants'; +import { handleRateLimit } from './middlewares'; const intlMiddleware = createMiddleware(routing); export default function middleware(request: NextRequest) { + const publicRoutes = ['/api/newsletter/subscribe', ...PUBLIC_ROUTES]; + + if (request.nextUrl.pathname.startsWith('/api/')) { + const isAuthRoute = request.nextUrl.pathname.startsWith('/api/auth/'); + const isInternalRoute = request.nextUrl.pathname.startsWith('/api/internal/'); + + if (!isAuthRoute && !isInternalRoute) { + return handleRateLimit(request); + } + + return NextResponse.next(); + } + + const publicPathnameRegex = RegExp( + `^(/(${routing.locales.join('|')}))?(${publicRoutes + .flatMap((p) => (p === '/' ? ['', '/'] : p)) + .join('|')})/?$`, + 'i' + ); + + const isPublicPage = publicPathnameRegex.test(request.nextUrl.pathname); + + if (isPublicPage) { + return intlMiddleware(request); + } + + // Check for session token in cookies + const sessionToken = request.cookies.get( + process.env.NODE_ENV === 'production' + ? '__Secure-authjs.session-token' + : 'authjs.session-token' + ); + + if (!sessionToken?.value) { + // Redirect to login page with locale support + const signInUrl = new URL('/login', request.url); + if (request.nextUrl.pathname !== '/login') { + signInUrl.searchParams.set('callbackUrl', request.url); + } + return NextResponse.redirect(signInUrl); + } + return intlMiddleware(request); } From 8ac44f7901f2b2b7ded1be0f65a3a341eb5d003e Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Sat, 11 Oct 2025 20:39:06 +0300 Subject: [PATCH 05/49] feat: Adds authentication components Implements sign-in and sign-up functionality. Introduces auth link component for consistent styling. Integrates i18n for internationalization of auth-related messages. Adds sign-in and sign-up buttons to the header. --- messages/en/auth.json | 35 ++++++++++++++++++- messages/tr/auth.json | 31 ++++++++++++++++- src/components/auth/auth-link.tsx | 45 +++++++++++++++++++++++++ src/components/auth/index.ts | 3 ++ src/components/auth/signin-button.tsx | 15 +++++++++ src/components/auth/signout-button.tsx | 20 +++++++++++ src/components/auth/signup-button.tsx | 16 +++++++++ src/components/layout/header/header.tsx | 3 ++ 8 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 src/components/auth/auth-link.tsx create mode 100644 src/components/auth/index.ts create mode 100644 src/components/auth/signin-button.tsx create mode 100644 src/components/auth/signout-button.tsx create mode 100644 src/components/auth/signup-button.tsx diff --git a/messages/en/auth.json b/messages/en/auth.json index 0967ef4..9f923a1 100644 --- a/messages/en/auth.json +++ b/messages/en/auth.json @@ -1 +1,34 @@ -{} +{ + "signin": { + "title": "Sign In", + "description": "Welcome back! Please sign in to continue.", + "signing": "Signing in...", + "success": "Successfully signed in!", + "error": "Sign in failed", + "invalidCredentials": "Invalid email or password" + }, + "signup": { + "title": "Sign Up", + "description": "Join us to access exclusive features and content.", + "creating": "Creating account...", + "success": "Account created successfully!", + "successDescription": "You can now sign in with your credentials.", + "error": "Failed to create account", + "verificationRequired": "Account created! Please verify your email", + "verificationDescription": "We've sent a verification link to {email}. Please check your inbox and click the link to activate your account.", + "receiveUpdates": { + "label": "Receive updates", + "description": "Be the first to know about the latest features, updates and special announcements!" + } + }, + "signout": "Sign Out", + "signinWithGoogle": "Sign In with Google", + "signinWithFacebook": "Sign In with Facebook", + "signinWithTwitter": "Sign In with Twitter", + "signinWithGithub": "Sign In with Github", + "signinWithApple": "Sign In with Apple", + "signinWithLinkedIn": "Sign In with LinkedIn", + "signinWithGitlab": "Sign In with GitLab", + "signinWithMicrosoft": "Sign In with Microsoft", + "signinWithInstagram": "Sign In with Instagram" +} diff --git a/messages/tr/auth.json b/messages/tr/auth.json index 0967ef4..7b892f4 100644 --- a/messages/tr/auth.json +++ b/messages/tr/auth.json @@ -1 +1,30 @@ -{} +{ + "signin": { + "title": "Giriş Yap", + "description": "Tekrar hoş geldiniz! Devam etmek için lütfen giriş yapın.", + "signing": "Giriş yapılıyor...", + "success": "Başarıyla giriş yapıldı!", + "error": "Giriş yapılamadı", + "invalidCredentials": "Geçersiz e-posta veya şifre" + }, + "signup": { + "title": "Kayıt Ol", + "description": "Özel özelliklere ve içeriğe erişmek için bize katılın.", + "creating": "Hesap oluşturuluyor...", + "success": "Hesap başarıyla oluşturuldu!", + "successDescription": "Artık kimlik bilgilerinizle giriş yapabilirsiniz.", + "error": "Hesap oluşturulamadı", + "verificationRequired": "Hesap oluşturuldu! Lütfen e-postanızı doğrulayın", + "verificationDescription": "{email} adresine bir doğrulama bağlantısı gönderdik. Lütfen gelen kutunuzu kontrol edin ve hesabınızı etkinleştirmek için bağlantıya tıklayın." + }, + "signout": "Çıkış Yap", + "signinWithGoogle": "Google ile Giriş Yap", + "signinWithFacebook": "Facebook ile Giriş Yap", + "signinWithTwitter": "Twitter ile Giriş Yap", + "signinWithGithub": "Github ile Giriş Yap", + "signinWithApple": "Apple ile Giriş Yap", + "signinWithLinkedIn": "LinkedIn ile Giriş Yap", + "signinWithGitlab": "GitLab ile Giriş Yap", + "signinWithMicrosoft": "Microsoft ile Giriş Yap", + "signinWithInstagram": "Instagram ile Giriş Yap" +} diff --git a/src/components/auth/auth-link.tsx b/src/components/auth/auth-link.tsx new file mode 100644 index 0000000..e64d2c4 --- /dev/null +++ b/src/components/auth/auth-link.tsx @@ -0,0 +1,45 @@ +'use client'; + +import { ReactNode } from 'react'; +import { Button, buttonVariants } from '@/components/ui'; +import { cn } from '@/lib'; +import { Link } from '@/lib/i18n/navigation'; + +interface AuthLinkProps { + href?: string; + variant?: NonNullable[0]>['variant']; + size?: NonNullable[0]>['size']; + children: ReactNode; + className?: string; + onClick?: () => void; +} + +export function AuthLink({ href, variant, size, children, className, onClick }: AuthLinkProps) { + if (onClick && !href) { + return ( + + ); + } + + if (href) { + return ( + + ); + } + + return ( + + ); +} diff --git a/src/components/auth/index.ts b/src/components/auth/index.ts new file mode 100644 index 0000000..9e9d1ca --- /dev/null +++ b/src/components/auth/index.ts @@ -0,0 +1,3 @@ +export * from './signup-button'; +export * from './signin-button'; +export * from './signout-button'; diff --git a/src/components/auth/signin-button.tsx b/src/components/auth/signin-button.tsx new file mode 100644 index 0000000..0e889b3 --- /dev/null +++ b/src/components/auth/signin-button.tsx @@ -0,0 +1,15 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { AuthLink } from './auth-link'; +import { LogIn as IconLogIn } from 'lucide-react'; + +export function SignInButton() { + const t = useTranslations('auth'); + return ( + + + {t('signin.title')} + + ); +} diff --git a/src/components/auth/signout-button.tsx b/src/components/auth/signout-button.tsx new file mode 100644 index 0000000..3fd119b --- /dev/null +++ b/src/components/auth/signout-button.tsx @@ -0,0 +1,20 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { AuthLink } from './auth-link'; +import { signOut } from 'next-auth/react'; + +export function SignOutButton() { + const t = useTranslations('auth'); + return ( + { + void signOut(); + }} + > + {t('signout')} + + ); +} diff --git a/src/components/auth/signup-button.tsx b/src/components/auth/signup-button.tsx new file mode 100644 index 0000000..fdd23c4 --- /dev/null +++ b/src/components/auth/signup-button.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { AuthLink } from './auth-link'; + +export function SignUpButton() { + const t = useTranslations('auth'); + return ( + + {t('signup.title')} + + ); +} diff --git a/src/components/layout/header/header.tsx b/src/components/layout/header/header.tsx index 102230d..01fcb42 100644 --- a/src/components/layout/header/header.tsx +++ b/src/components/layout/header/header.tsx @@ -4,6 +4,7 @@ import { Navbar } from '@/components/navigation'; import { Logo } from '@/components/shared/'; import { useStickyNavbar } from '@/hooks'; import { CompactLocaleSwitcher } from '@/components/switchers'; +import { SignInButton, SignUpButton } from '@/components/auth'; export function Header() { const sticky = useStickyNavbar(); @@ -24,6 +25,8 @@ export function Header() {

+ +
From e9cf00d3bbcc53a765ddd0e98595547cfab2fe03 Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Sat, 11 Oct 2025 20:51:00 +0300 Subject: [PATCH 06/49] feat: Configures authentication flow - Introduces authentication routes and constants for consistent navigation. - Adds a component for signing in with various providers using icons and translations. - Refactors authentication logic into a dedicated module. --- src/app/api/auth/[...nextauth]/route.ts | 2 +- src/components/auth/sign-with-button.tsx | 127 +++++++++++++++++++++++ src/components/auth/signin-button.tsx | 3 +- src/components/auth/signup-button.tsx | 3 +- src/{ => lib/auth}/auth.ts | 13 +-- src/lib/auth/constants.ts | 13 +++ src/lib/auth/providers.ts | 57 ++++++++++ 7 files changed, 209 insertions(+), 9 deletions(-) create mode 100644 src/components/auth/sign-with-button.tsx rename src/{ => lib/auth}/auth.ts (82%) create mode 100644 src/lib/auth/constants.ts create mode 100644 src/lib/auth/providers.ts diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index 0aa1cd7..343f7a5 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -1,2 +1,2 @@ -import { handlers } from '@/auth'; +import { handlers } from '@/lib/auth/auth'; export const { GET, POST } = handlers; diff --git a/src/components/auth/sign-with-button.tsx b/src/components/auth/sign-with-button.tsx new file mode 100644 index 0000000..cb782b9 --- /dev/null +++ b/src/components/auth/sign-with-button.tsx @@ -0,0 +1,127 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import * as React from 'react'; + +import { Button, buttonVariants } from '@/components/ui/button'; +import { cn } from '@/lib'; +import { Provider } from '@/lib/auth/providers'; +import { + GoogleIcon, + GithubIcon, + GitlabIcon, + AppleIcon, + InstagramIcon, + FacebookIcon, + TwitterIcon, + LinkedinIcon +} from '@/components/icons'; +import { signIn } from '@/lib/auth/auth'; +import { AUTH_ROUTES } from '@/lib/auth/constants'; + +type ButtonVariantProps = NonNullable[0]>; + +function getProviderDetail({ t, provider }: { t: (key: string) => string; provider: Provider }): { + text: string; + Icon: React.ComponentType<{ className?: string; color?: string }>; +} { + switch (provider) { + case 'google': + return { + text: t('auth.signinWithGoogle'), + Icon: GoogleIcon + }; + case 'github': + return { + text: t('auth.signinWithGithub'), + Icon: GithubIcon + }; + case 'gitlab': + return { + text: t('auth.signinWithGitlab'), + Icon: GitlabIcon + }; + case 'apple': + return { + text: t('auth.signinWithApple'), + Icon: AppleIcon + }; + case 'instagram': + return { + text: t('auth.signinWithInstagram'), + Icon: InstagramIcon + }; + case 'facebook': + return { + text: t('auth.signinWithFacebook'), + Icon: FacebookIcon + }; + case 'twitter': + return { + text: t('auth.signinWithTwitter'), + Icon: TwitterIcon + }; + case 'linkedin': + return { + text: t('auth.signinWithLinkedIn'), + Icon: LinkedinIcon + }; + default: + throw new Error('Unsupported provider'); + } +} + +type SignWithButtonProps = React.ComponentPropsWithoutRef<'button'> & + ButtonVariantProps & { + provider: Provider; + onlyIcon?: boolean; + asChild?: boolean; + className?: string; + }; + +function SignWithButton({ + provider, + className, + variant, + size, + onlyIcon = false, + asChild = false, + ...props +}: SignWithButtonProps) { + const t = useTranslations(); + const { text, Icon } = getProviderDetail({ t: (key) => t(key as any), provider }); + + return ( +
{ + 'use server'; + + await signIn(provider, { redirectTo: AUTH_ROUTES.DASHBOARD }); + }} + className={cn('w-full', className)} + > + +
+ ); +} + +export { SignWithButton }; diff --git a/src/components/auth/signin-button.tsx b/src/components/auth/signin-button.tsx index 0e889b3..8f75bcf 100644 --- a/src/components/auth/signin-button.tsx +++ b/src/components/auth/signin-button.tsx @@ -3,11 +3,12 @@ import { useTranslations } from 'next-intl'; import { AuthLink } from './auth-link'; import { LogIn as IconLogIn } from 'lucide-react'; +import { AUTH_ROUTES } from '@/lib/auth/constants'; export function SignInButton() { const t = useTranslations('auth'); return ( - + {t('signin.title')} diff --git a/src/components/auth/signup-button.tsx b/src/components/auth/signup-button.tsx index fdd23c4..fc40f55 100644 --- a/src/components/auth/signup-button.tsx +++ b/src/components/auth/signup-button.tsx @@ -2,13 +2,14 @@ import { useTranslations } from 'next-intl'; import { AuthLink } from './auth-link'; +import { AUTH_ROUTES } from '@/lib/auth/constants'; export function SignUpButton() { const t = useTranslations('auth'); return ( {t('signup.title')} diff --git a/src/auth.ts b/src/lib/auth/auth.ts similarity index 82% rename from src/auth.ts rename to src/lib/auth/auth.ts index 68bd66a..2addf4c 100644 --- a/src/auth.ts +++ b/src/lib/auth/auth.ts @@ -1,16 +1,17 @@ import NextAuth from 'next-auth'; import { PrismaAdapter } from '@auth/prisma-adapter'; -import { prisma } from './lib/prisma'; +import { prisma } from '../prisma'; +import { AUTH_ROUTES } from './constants'; export const { handlers, auth, signIn, signOut } = NextAuth({ adapter: PrismaAdapter(prisma), providers: [], pages: { - signIn: '/login', - signOut: '/logout', - error: '/error', - verifyRequest: '/verify-request', - newUser: '/register' + signIn: AUTH_ROUTES.SIGN_IN, + signOut: AUTH_ROUTES.SIGN_OUT, + newUser: AUTH_ROUTES.NEW_USER, + error: AUTH_ROUTES.ERROR, + verifyRequest: AUTH_ROUTES.VERIFY_REQUEST }, session: { strategy: 'jwt', diff --git a/src/lib/auth/constants.ts b/src/lib/auth/constants.ts new file mode 100644 index 0000000..9e4ae79 --- /dev/null +++ b/src/lib/auth/constants.ts @@ -0,0 +1,13 @@ +/** + * NextAuth.js tarafından kullanılan varsayılan yönlendirme rotalarını tanımlar. + * Bu sabitleri projenin herhangi bir yerinde kullanmak, tek bir kaynak (Source of Truth) sağlar. + */ +export const AUTH_ROUTES = { + SIGN_IN: '/login', + SIGN_OUT: '/logout', + SIGN_UP: '/register', + ERROR: '/error', + VERIFY_REQUEST: '/verify-request', + NEW_USER: '/register', + DASHBOARD: '/dashboard' // Sizin kullandığınız yönlendirme hedefi +} as const; diff --git a/src/lib/auth/providers.ts b/src/lib/auth/providers.ts new file mode 100644 index 0000000..38111e1 --- /dev/null +++ b/src/lib/auth/providers.ts @@ -0,0 +1,57 @@ +import { + GoogleIcon, + FacebookIcon, + TwitterIcon, + GithubIcon, + GitlabIcon, + LinkedinIcon +} from '@/components/icons/brands'; + +export type Provider = + | 'google' + | 'github' + | 'gitlab' + | 'apple' + | 'instagram' + | 'facebook' + | 'twitter' + | 'linkedin'; + +export interface ProviderConfig { + id: Provider; + name: string; + logo: React.ComponentType<{ className?: string; color?: string }>; +} + +export const providers: ProviderConfig[] = [ + { + id: 'google', + name: 'Google', + logo: GoogleIcon + }, + { + id: 'facebook', + name: 'Facebook', + logo: FacebookIcon + }, + { + id: 'twitter', + name: 'Twitter', + logo: TwitterIcon + }, + { + id: 'linkedin', + name: 'LinkedIn', + logo: LinkedinIcon + }, + { + id: 'github', + name: 'GitHub', + logo: GithubIcon + }, + { + id: 'gitlab', + name: 'GitLab', + logo: GitlabIcon + } +]; From 5938a5aeedee80a304e62e38bbb099e90833603e Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Sat, 11 Oct 2025 21:31:02 +0300 Subject: [PATCH 07/49] feat: Adds authentication with multiple providers Adds support for authentication using Resend, Google, GitHub, GitLab, Facebook, Apple, Instagram, Twitter, and LinkedIn. This enhances the user experience by providing multiple authentication options. --- .env.example | 19 ++++ messages/declarations.json | 35 +++++- src/lib/auth/auth.ts | 215 ++++++++++++++++++++++++++++++++++++- src/types/auth.ts | 31 ++++++ 4 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 src/types/auth.ts diff --git a/.env.example b/.env.example index 8a3dfc8..89ecdae 100644 --- a/.env.example +++ b/.env.example @@ -15,3 +15,22 @@ RESEND_API_KEY=---resend_api_key--- RESEND_AUDIENCE_ID=---example-value--- RESEND_FROM_EMAIL=noreply@example.com UPSTASH_REDIS_REST_URL=https://example.com/api +UPSTASH_REDIS_REST_TOKEN=---upstash_redis_rest_token--- + +GOOGLE_CLIENT_ID=---example-value--- +GOOGLE_CLIENT_SECRET=---google_client_secret--- +GITHUB_CLIENT_ID=---example-value--- +GITHUB_CLIENT_SECRET=---github_client_secret--- +GITLAB_CLIENT_ID=---example-value--- +GITLAB_CLIENT_SECRET=---gitlab_client_secret--- +FACEBOOK_CLIENT_ID=---example-value--- +FACEBOOK_CLIENT_SECRET=---facebook_client_secret--- +TWITTER_CLIENT_ID=---example-value--- +TWITTER_CLIENT_SECRET=---twitter_client_secret--- +LINKEDIN_CLIENT_ID=---example-value--- +LINKEDIN_CLIENT_SECRET=---linkedin_client_secret--- +APPLE_CLIENT_ID=---example-value--- +APPLE_CLIENT_SECRET=---apple_client_secret--- +INSTAGRAM_CLIENT_ID=---example-value--- +INSTAGRAM_CLIENT_SECRET=---instagram_client_secret--- + diff --git a/messages/declarations.json b/messages/declarations.json index fe5b2c5..dffac0a 100644 --- a/messages/declarations.json +++ b/messages/declarations.json @@ -209,7 +209,40 @@ "returnHome": "Return to Homepage" } }, - "auth": {}, + "auth": { + "signin": { + "title": "Sign In", + "description": "Welcome back! Please sign in to continue.", + "signing": "Signing in...", + "success": "Successfully signed in!", + "error": "Sign in failed", + "invalidCredentials": "Invalid email or password" + }, + "signup": { + "title": "Sign Up", + "description": "Join us to access exclusive features and content.", + "creating": "Creating account...", + "success": "Account created successfully!", + "successDescription": "You can now sign in with your credentials.", + "error": "Failed to create account", + "verificationRequired": "Account created! Please verify your email", + "verificationDescription": "We've sent a verification link to {email}. Please check your inbox and click the link to activate your account.", + "receiveUpdates": { + "label": "Receive updates", + "description": "Be the first to know about the latest features, updates and special announcements!" + } + }, + "signout": "Sign Out", + "signinWithGoogle": "Sign In with Google", + "signinWithFacebook": "Sign In with Facebook", + "signinWithTwitter": "Sign In with Twitter", + "signinWithGithub": "Sign In with Github", + "signinWithApple": "Sign In with Apple", + "signinWithLinkedIn": "Sign In with LinkedIn", + "signinWithGitlab": "Sign In with GitLab", + "signinWithMicrosoft": "Sign In with Microsoft", + "signinWithInstagram": "Sign In with Instagram" + }, "contact": { "title": "Contact", "description": "You can use the information below to get in touch with us.", diff --git a/src/lib/auth/auth.ts b/src/lib/auth/auth.ts index 2addf4c..f3857d4 100644 --- a/src/lib/auth/auth.ts +++ b/src/lib/auth/auth.ts @@ -1,11 +1,224 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + import NextAuth from 'next-auth'; import { PrismaAdapter } from '@auth/prisma-adapter'; import { prisma } from '../prisma'; import { AUTH_ROUTES } from './constants'; +import GitHub from 'next-auth/providers/github'; +import GitLab from 'next-auth/providers/gitlab'; +import Google from 'next-auth/providers/google'; +import Resend from 'next-auth/providers/resend'; +import Apple from 'next-auth/providers/apple'; +import Instagram from 'next-auth/providers/instagram'; +import Facebook from 'next-auth/providers/facebook'; +import Twitter from 'next-auth/providers/twitter'; +import LinkedIn from 'next-auth/providers/linkedin'; +import { NextAuthProfileReturnType } from '@/types/auth'; + +const defaultLocale = 'en'; +const defaultTheme = 'system'; +const defaultRole = 'User'; +const defaultReceiveUpdates = true; export const { handlers, auth, signIn, signOut } = NextAuth({ adapter: PrismaAdapter(prisma), - providers: [], + providers: [ + Resend, + Google({ + clientId: process.env.GOOGLE_CLIENT_ID!, + clientSecret: process.env.GOOGLE_CLIENT_SECRET!, + profile(profile: any): NextAuthProfileReturnType { + return { + id: profile.sub, + email: profile.email || '', + name: profile.name || null, + image: profile.picture || null, + firstName: profile.given_name || null, + lastName: profile.family_name || null, + role: defaultRole, + locale: profile.locale || defaultLocale, + theme: defaultTheme, + receiveUpdates: defaultReceiveUpdates, + twoFactorEnabled: false, + emailVerified: true, + createdAt: new Date(), + updatedAt: new Date(), + isActive: true + } as NextAuthProfileReturnType; + } + }), + GitHub({ + clientId: process.env.GITHUB_CLIENT_ID!, + clientSecret: process.env.GITHUB_CLIENT_SECRET!, + profile(profile: any): NextAuthProfileReturnType { + return { + id: profile.id?.toString() || '', + email: profile.email || '', + name: profile.name || profile.login || null, + image: profile.avatar_url || null, + role: defaultRole, + locale: defaultLocale, + theme: defaultTheme, + receiveUpdates: defaultReceiveUpdates, + twoFactorEnabled: false, + emailVerified: true, + createdAt: new Date(), + updatedAt: new Date(), + isActive: true + } as NextAuthProfileReturnType; + } + }), + GitLab({ + clientId: process.env.GITLAB_CLIENT_ID!, + clientSecret: process.env.GITLAB_CLIENT_SECRET!, + profile(profile: any): NextAuthProfileReturnType { + return { + id: profile.id?.toString() || '', + email: profile.email || '', + name: profile.name || null, + image: profile.avatar_url || null, + role: defaultRole, + locale: defaultLocale, + theme: defaultTheme, + receiveUpdates: defaultReceiveUpdates, + twoFactorEnabled: false, + emailVerified: true, + createdAt: new Date(), + updatedAt: new Date(), + isActive: true + } as NextAuthProfileReturnType; + } + }), + Facebook({ + clientId: process.env.FACEBOOK_CLIENT_ID!, + clientSecret: process.env.FACEBOOK_CLIENT_SECRET!, + profile(profile: any): NextAuthProfileReturnType { + return { + id: profile.id, + email: profile.email || '', + name: profile.name || null, + image: profile.picture?.data?.url || null, + role: defaultRole, + locale: profile.locale || defaultLocale, + theme: defaultTheme, + receiveUpdates: defaultReceiveUpdates, + twoFactorEnabled: false, + emailVerified: true, + createdAt: new Date(), + updatedAt: new Date(), + isActive: true + } as NextAuthProfileReturnType; + } + }), + Apple({ + clientId: process.env.APPLE_CLIENT_ID!, + clientSecret: process.env.APPLE_CLIENT_SECRET!, + profile(profile: any): NextAuthProfileReturnType { + return { + id: profile.sub, + email: profile.email || '', + name: + profile.name || + `${profile.firstName || ''} ${profile.lastName || ''}`.trim() || + null, + image: profile.picture || null, + role: defaultRole, + locale: defaultLocale, + theme: defaultTheme, + receiveUpdates: defaultReceiveUpdates, + twoFactorEnabled: false, + emailVerified: true, + createdAt: new Date(), + updatedAt: new Date(), + isActive: true + } as NextAuthProfileReturnType; + } + }), + Instagram({ + clientId: process.env.INSTAGRAM_CLIENT_ID!, + clientSecret: process.env.INSTAGRAM_CLIENT_SECRET!, + profile(profile: any): NextAuthProfileReturnType { + return { + id: profile.id?.toString() || '', + email: profile.email || '', + name: profile.name || profile.username || null, + image: profile.picture || null, + role: defaultRole, + locale: defaultLocale, + theme: defaultTheme, + receiveUpdates: defaultReceiveUpdates, + twoFactorEnabled: false, + emailVerified: !!profile.email, + createdAt: new Date(), + updatedAt: new Date(), + isActive: true + } as NextAuthProfileReturnType; + } + }), + Twitter({ + clientId: process.env.TWITTER_CLIENT_ID!, + clientSecret: process.env.TWITTER_CLIENT_SECRET!, + profile(profile: any): NextAuthProfileReturnType { + const data = profile.data || profile; + return { + id: data.id?.toString() || '', + email: data.email || '', + name: data.name || data.username || null, + image: data.profile_image_url?.replace('_normal', '_400x400') || null, + firstName: data.name?.split(' ')[0] || null, + lastName: data.name?.split(' ').slice(1).join(' ') || null, + username: data.username || null, + role: defaultRole, + locale: defaultLocale, + theme: defaultTheme, + receiveUpdates: defaultReceiveUpdates, + twoFactorEnabled: false, + emailVerified: !!data.email, + createdAt: new Date(), + updatedAt: new Date(), + isActive: true + } as NextAuthProfileReturnType; + } + }), + LinkedIn({ + clientId: process.env.LINKEDIN_CLIENT_ID!, + clientSecret: process.env.LINKEDIN_CLIENT_SECRET!, + profile(profile: any): NextAuthProfileReturnType { + const email = profile.email || profile.emailAddress || ''; + const firstName = profile.firstName || profile.given_name || null; + const lastName = profile.lastName || profile.family_name || null; + return { + id: profile.id || profile.sub || '', + email: email, + name: + profile.name || + `${firstName} ${lastName}`.trim() || + profile.localizedFirstName || + null, + image: + profile.picture || + profile.profilePicture?.displayImage || + profile['profilePicture(displayImage~:playableStreams)']?.displayImage + ?.elements?.[0]?.identifiers?.[0]?.identifier || + null, + firstName: firstName, + lastName: lastName, + username: profile.vanityName || null, + role: defaultRole, + locale: profile.locale || defaultLocale, + theme: defaultTheme, + receiveUpdates: defaultReceiveUpdates, + twoFactorEnabled: false, + emailVerified: !!email, + createdAt: new Date(), + updatedAt: new Date(), + isActive: true + } as NextAuthProfileReturnType; + } + }) + ], pages: { signIn: AUTH_ROUTES.SIGN_IN, signOut: AUTH_ROUTES.SIGN_OUT, diff --git a/src/types/auth.ts b/src/types/auth.ts new file mode 100644 index 0000000..ebcc082 --- /dev/null +++ b/src/types/auth.ts @@ -0,0 +1,31 @@ +import type { User } from 'next-auth'; + +/** + * Defines the type returned from the Auth.js provider 'profile' callback, + * which maps to the application's database user schema. + */ +export interface UserDbProfile { + id: string; + email: string; + name: string | null; + image: string | null; + firstName: string | null; + lastName: string | null; + username?: string | null; + role: string; + locale: string; + theme: string; + receiveUpdates: boolean; + twoFactorEnabled: boolean; + emailVerified: boolean; + createdAt: Date; + updatedAt: Date; + isActive: boolean; + [key: string]: any; // For additional fields (e.g., extra data from Google) +} + +/** + * Defines the expected return type for Auth.js profile functions. + * This type is an intersection of Auth.js's base User type and the custom UserDbProfile. + */ +export type NextAuthProfileReturnType = UserDbProfile & User; From 0ef975b5c9ebe9bae1d77d9b2f82ca50f876516f Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Sun, 12 Oct 2025 02:51:21 +0300 Subject: [PATCH 08/49] fix: Refactors page component naming Renames 'Home' and 'PrivacyPolicyPage' components to 'Page' for consistency. Simplifies data fetching in the privacy policy page by using `getTranslations` directly. --- src/app/[locale]/(website)/page.tsx | 2 +- src/app/[locale]/(website)/privacy/page.tsx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/[locale]/(website)/page.tsx b/src/app/[locale]/(website)/page.tsx index 32f0e10..47e4788 100644 --- a/src/app/[locale]/(website)/page.tsx +++ b/src/app/[locale]/(website)/page.tsx @@ -12,7 +12,7 @@ import { NewsletterConfirmDialog } from '@/components/newsletter'; import { Testimonials } from '@/components/testimonial'; import { Hero, GitHubSection } from './home/components'; -export default function Home() { +export default function Page() { return (
diff --git a/src/app/[locale]/(website)/privacy/page.tsx b/src/app/[locale]/(website)/privacy/page.tsx index e0a0b93..cf12d7f 100644 --- a/src/app/[locale]/(website)/privacy/page.tsx +++ b/src/app/[locale]/(website)/privacy/page.tsx @@ -1,6 +1,5 @@ import { Metadata } from 'next'; import { getTranslations } from 'next-intl/server'; -import { useTranslations } from 'next-intl'; import { generatePageMetadata } from '@/lib'; export async function generateMetadata(): Promise { @@ -13,8 +12,8 @@ export async function generateMetadata(): Promise { }); } -export default function PrivacyPolicyPage() { - const t = useTranslations('app.privacyPolicy'); +export default async function Page() { + const t = await getTranslations('app.privacyPolicy'); return (

{t('title')}

From d9d429650ab2c11fc30329bfd4225a86ad05a6a2 Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Sun, 12 Oct 2025 20:33:24 +0300 Subject: [PATCH 09/49] fix: Fixes loading component import paths Fixes broken imports for loading components by updating the import paths. Removes an unused import from the manifest file. --- src/app/[locale]/(website)/about/loading.tsx | 2 +- src/app/[locale]/(website)/contact/loading.tsx | 2 +- src/app/[locale]/(website)/faq/loading.tsx | 2 +- src/app/[locale]/(website)/pricing/loading.tsx | 2 +- src/app/[locale]/(website)/pricing/page.tsx | 1 - src/app/[locale]/(website)/privacy/loading.tsx | 2 +- src/app/manifest.ts | 1 - src/components/shared/index.ts | 1 - 8 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/app/[locale]/(website)/about/loading.tsx b/src/app/[locale]/(website)/about/loading.tsx index 69eea9a..a5c1ad5 100644 --- a/src/app/[locale]/(website)/about/loading.tsx +++ b/src/app/[locale]/(website)/about/loading.tsx @@ -1,4 +1,4 @@ -import { PageLoading } from '@/components/shared'; +import { PageLoading } from '@/components/shared/page-loading'; export default function Loading() { return ; diff --git a/src/app/[locale]/(website)/contact/loading.tsx b/src/app/[locale]/(website)/contact/loading.tsx index 69eea9a..a5c1ad5 100644 --- a/src/app/[locale]/(website)/contact/loading.tsx +++ b/src/app/[locale]/(website)/contact/loading.tsx @@ -1,4 +1,4 @@ -import { PageLoading } from '@/components/shared'; +import { PageLoading } from '@/components/shared/page-loading'; export default function Loading() { return ; diff --git a/src/app/[locale]/(website)/faq/loading.tsx b/src/app/[locale]/(website)/faq/loading.tsx index 69eea9a..a5c1ad5 100644 --- a/src/app/[locale]/(website)/faq/loading.tsx +++ b/src/app/[locale]/(website)/faq/loading.tsx @@ -1,4 +1,4 @@ -import { PageLoading } from '@/components/shared'; +import { PageLoading } from '@/components/shared/page-loading'; export default function Loading() { return ; diff --git a/src/app/[locale]/(website)/pricing/loading.tsx b/src/app/[locale]/(website)/pricing/loading.tsx index 69eea9a..a5c1ad5 100644 --- a/src/app/[locale]/(website)/pricing/loading.tsx +++ b/src/app/[locale]/(website)/pricing/loading.tsx @@ -1,4 +1,4 @@ -import { PageLoading } from '@/components/shared'; +import { PageLoading } from '@/components/shared/page-loading'; export default function Loading() { return ; diff --git a/src/app/[locale]/(website)/pricing/page.tsx b/src/app/[locale]/(website)/pricing/page.tsx index 87e47f0..c3a200d 100644 --- a/src/app/[locale]/(website)/pricing/page.tsx +++ b/src/app/[locale]/(website)/pricing/page.tsx @@ -10,7 +10,6 @@ import { PageHero } from '@/components/layout'; export async function generateMetadata(): Promise { const t = await getTranslations('pricing'); - return await generatePageMetadata({ params: Promise.resolve({ title: t('title'), diff --git a/src/app/[locale]/(website)/privacy/loading.tsx b/src/app/[locale]/(website)/privacy/loading.tsx index 69eea9a..a5c1ad5 100644 --- a/src/app/[locale]/(website)/privacy/loading.tsx +++ b/src/app/[locale]/(website)/privacy/loading.tsx @@ -1,4 +1,4 @@ -import { PageLoading } from '@/components/shared'; +import { PageLoading } from '@/components/shared/page-loading'; export default function Loading() { return ; diff --git a/src/app/manifest.ts b/src/app/manifest.ts index b7e09fe..2e90e46 100644 --- a/src/app/manifest.ts +++ b/src/app/manifest.ts @@ -2,7 +2,6 @@ import { getLocale, getTranslations } from 'next-intl/server'; import { getLangDir } from 'rtl-detect'; import { getBaseUrl } from '@/lib'; import type { MetadataRoute } from 'next'; -import 'next/dist/lib/metadata/types/manifest-types'; export default async function manifest(): Promise { const baseUrl = getBaseUrl(); diff --git a/src/components/shared/index.ts b/src/components/shared/index.ts index 993a7ff..e17a58c 100644 --- a/src/components/shared/index.ts +++ b/src/components/shared/index.ts @@ -9,4 +9,3 @@ export * from './small-loading'; export * from './text-rotator'; export * from './themed-image'; export * from './version'; -export * from './page-loading'; From 9f371efaf2cdef1c85d1274ea77e203f4b85bcf6 Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Mon, 13 Oct 2025 22:42:25 +0300 Subject: [PATCH 10/49] feat: scaffolds authentication flow Adds basic authentication pages. Implements layout for authentication. Updates redirect after sign-in. Adds .notes to .gitignore. --- .gitignore | 4 +++- src/app/[locale]/(auth)/error/page.tsx | 5 +++++ src/app/[locale]/(auth)/layout.tsx | 7 +++++++ src/app/[locale]/(auth)/login/page.tsx | 5 +++++ src/app/[locale]/(auth)/password/new/page.tsx | 5 +++++ .../[locale]/(auth)/password/reset-sent/page.tsx | 5 +++++ src/app/[locale]/(auth)/password/reset/page.tsx | 5 +++++ src/app/[locale]/(auth)/register/page.tsx | 5 +++++ src/app/[locale]/(auth)/verify/email-sent/page.tsx | 5 +++++ src/app/[locale]/(auth)/verify/email/page.tsx | 5 +++++ src/app/[locale]/app/invoices/[invoiceId]/page.tsx | 13 +++++++++++++ src/app/[locale]/app/invoices/page.tsx | 5 +++++ src/app/[locale]/app/layout.tsx | 7 +++++++ src/app/[locale]/app/page.tsx | 5 +++++ src/app/[locale]/app/tickets/[ticketId]/page.tsx | 13 +++++++++++++ src/app/[locale]/app/tickets/page.tsx | 5 +++++ src/components/auth/sign-with-button.tsx | 2 +- src/lib/auth/constants.ts | 2 +- 18 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 src/app/[locale]/(auth)/error/page.tsx create mode 100644 src/app/[locale]/(auth)/layout.tsx create mode 100644 src/app/[locale]/(auth)/login/page.tsx create mode 100644 src/app/[locale]/(auth)/password/new/page.tsx create mode 100644 src/app/[locale]/(auth)/password/reset-sent/page.tsx create mode 100644 src/app/[locale]/(auth)/password/reset/page.tsx create mode 100644 src/app/[locale]/(auth)/register/page.tsx create mode 100644 src/app/[locale]/(auth)/verify/email-sent/page.tsx create mode 100644 src/app/[locale]/(auth)/verify/email/page.tsx create mode 100644 src/app/[locale]/app/invoices/[invoiceId]/page.tsx create mode 100644 src/app/[locale]/app/invoices/page.tsx create mode 100644 src/app/[locale]/app/layout.tsx create mode 100644 src/app/[locale]/app/page.tsx create mode 100644 src/app/[locale]/app/tickets/[ticketId]/page.tsx create mode 100644 src/app/[locale]/app/tickets/page.tsx diff --git a/.gitignore b/.gitignore index 6cb5670..eb58461 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,6 @@ playwright-report/ test-results/ /src/generated -messages/*.d.json.ts \ No newline at end of file +messages/*.d.json.ts + +.notes \ No newline at end of file diff --git a/src/app/[locale]/(auth)/error/page.tsx b/src/app/[locale]/(auth)/error/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/(auth)/error/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/app/[locale]/(auth)/layout.tsx b/src/app/[locale]/(auth)/layout.tsx new file mode 100644 index 0000000..50fb2e3 --- /dev/null +++ b/src/app/[locale]/(auth)/layout.tsx @@ -0,0 +1,7 @@ +export default function AuthLayout({ + children +}: Readonly<{ + children: React.ReactNode; +}>) { + return <>{children}; +} diff --git a/src/app/[locale]/(auth)/login/page.tsx b/src/app/[locale]/(auth)/login/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/(auth)/login/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/app/[locale]/(auth)/password/new/page.tsx b/src/app/[locale]/(auth)/password/new/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/(auth)/password/new/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/app/[locale]/(auth)/password/reset-sent/page.tsx b/src/app/[locale]/(auth)/password/reset-sent/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/(auth)/password/reset-sent/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/app/[locale]/(auth)/password/reset/page.tsx b/src/app/[locale]/(auth)/password/reset/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/(auth)/password/reset/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/app/[locale]/(auth)/register/page.tsx b/src/app/[locale]/(auth)/register/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/(auth)/register/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/app/[locale]/(auth)/verify/email-sent/page.tsx b/src/app/[locale]/(auth)/verify/email-sent/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/(auth)/verify/email-sent/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/app/[locale]/(auth)/verify/email/page.tsx b/src/app/[locale]/(auth)/verify/email/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/(auth)/verify/email/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/app/[locale]/app/invoices/[invoiceId]/page.tsx b/src/app/[locale]/app/invoices/[invoiceId]/page.tsx new file mode 100644 index 0000000..1624741 --- /dev/null +++ b/src/app/[locale]/app/invoices/[invoiceId]/page.tsx @@ -0,0 +1,13 @@ +'use client'; + +interface Props { + params: { + invoiceId: string; + }; +} + +export default function Page({ params }: Props) { + const { invoiceId } = params; + + return <>Page {invoiceId}; +} diff --git a/src/app/[locale]/app/invoices/page.tsx b/src/app/[locale]/app/invoices/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/app/invoices/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/app/[locale]/app/layout.tsx b/src/app/[locale]/app/layout.tsx new file mode 100644 index 0000000..4a10196 --- /dev/null +++ b/src/app/[locale]/app/layout.tsx @@ -0,0 +1,7 @@ +export default function AppLayout({ + children +}: Readonly<{ + children: React.ReactNode; +}>) { + return <>{children}; +} diff --git a/src/app/[locale]/app/page.tsx b/src/app/[locale]/app/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/app/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/app/[locale]/app/tickets/[ticketId]/page.tsx b/src/app/[locale]/app/tickets/[ticketId]/page.tsx new file mode 100644 index 0000000..0b1c767 --- /dev/null +++ b/src/app/[locale]/app/tickets/[ticketId]/page.tsx @@ -0,0 +1,13 @@ +'use client'; + +interface Props { + params: { + tickerId: string; + }; +} + +export default function Page({ params }: Props) { + const { tickerId } = params; + + return <>Page {tickerId}; +} diff --git a/src/app/[locale]/app/tickets/page.tsx b/src/app/[locale]/app/tickets/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/app/tickets/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/components/auth/sign-with-button.tsx b/src/components/auth/sign-with-button.tsx index cb782b9..e0cb521 100644 --- a/src/components/auth/sign-with-button.tsx +++ b/src/components/auth/sign-with-button.tsx @@ -96,7 +96,7 @@ function SignWithButton({ action={async () => { 'use server'; - await signIn(provider, { redirectTo: AUTH_ROUTES.DASHBOARD }); + await signIn(provider, { redirectTo: AUTH_ROUTES.APP }); }} className={cn('w-full', className)} > diff --git a/src/lib/auth/constants.ts b/src/lib/auth/constants.ts index 9e4ae79..814bf80 100644 --- a/src/lib/auth/constants.ts +++ b/src/lib/auth/constants.ts @@ -9,5 +9,5 @@ export const AUTH_ROUTES = { ERROR: '/error', VERIFY_REQUEST: '/verify-request', NEW_USER: '/register', - DASHBOARD: '/dashboard' // Sizin kullandığınız yönlendirme hedefi + APP: '/app' } as const; From b8eedfc363f537622f33349d4f1ff76bf1bba179 Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Mon, 13 Oct 2025 22:53:39 +0300 Subject: [PATCH 11/49] feat: Adds basic admin pages Adds initial layout and placeholder pages for admin section, tickets, and users. --- src/app/[locale]/admin/layout.tsx | 7 +++++++ src/app/[locale]/admin/page.tsx | 5 +++++ src/app/[locale]/admin/tickets/page.tsx | 5 +++++ src/app/[locale]/admin/users/page.tsx | 5 +++++ 4 files changed, 22 insertions(+) create mode 100644 src/app/[locale]/admin/layout.tsx create mode 100644 src/app/[locale]/admin/page.tsx create mode 100644 src/app/[locale]/admin/tickets/page.tsx create mode 100644 src/app/[locale]/admin/users/page.tsx diff --git a/src/app/[locale]/admin/layout.tsx b/src/app/[locale]/admin/layout.tsx new file mode 100644 index 0000000..4a10196 --- /dev/null +++ b/src/app/[locale]/admin/layout.tsx @@ -0,0 +1,7 @@ +export default function AppLayout({ + children +}: Readonly<{ + children: React.ReactNode; +}>) { + return <>{children}; +} diff --git a/src/app/[locale]/admin/page.tsx b/src/app/[locale]/admin/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/admin/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/app/[locale]/admin/tickets/page.tsx b/src/app/[locale]/admin/tickets/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/admin/tickets/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} diff --git a/src/app/[locale]/admin/users/page.tsx b/src/app/[locale]/admin/users/page.tsx new file mode 100644 index 0000000..0f49ebc --- /dev/null +++ b/src/app/[locale]/admin/users/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function Page() { + return <>Page; +} From c02e188a0c56442caa746b63822c74107ef0ffad Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Tue, 14 Oct 2025 23:26:30 +0300 Subject: [PATCH 12/49] Refactors website components structure Moves website-specific components to a dedicated directory, improving project organization and maintainability. Updates imports across the codebase to reflect the new component locations. --- src/app/[locale]/(website)/about/page.tsx | 2 +- src/app/[locale]/(website)/contact/page.tsx | 2 +- src/app/[locale]/(website)/home/components/hero.tsx | 4 ++-- src/app/[locale]/(website)/layout.tsx | 2 +- src/app/[locale]/(website)/page.tsx | 7 ++++--- src/app/[locale]/(website)/pricing/page.tsx | 4 ++-- src/app/[locale]/not-found.tsx | 2 +- src/components/shared/index.ts | 1 + src/components/{layout => shared}/library-logos.tsx | 0 src/components/{ => website}/banners/action-banner.tsx | 0 src/components/{ => website}/banners/compact-banner.tsx | 0 src/components/{ => website}/banners/confetti-banner.tsx | 0 src/components/{ => website}/banners/index.ts | 0 .../{ => website}/layout/background-patterns.tsx | 0 src/components/{ => website}/layout/call-to-action.tsx | 0 src/components/{ => website}/layout/footer/footer.tsx | 4 ++-- src/components/{ => website}/layout/footer/index.ts | 0 src/components/{ => website}/layout/header/header.tsx | 2 +- src/components/{ => website}/layout/header/index.ts | 0 src/components/{ => website}/layout/index.ts | 1 - src/components/{ => website}/layout/page-hero.tsx | 0 src/components/{ => website}/navigation/custom-link.tsx | 0 src/components/{ => website}/navigation/footer-menu.tsx | 0 src/components/{ => website}/navigation/index.ts | 0 src/components/{ => website}/navigation/navbar.tsx | 0 src/components/{ => website}/newsletter/confirm-dialog.tsx | 0 src/components/{ => website}/newsletter/form.tsx | 0 src/components/{ => website}/newsletter/index.ts | 0 src/components/{ => website}/testimonial/avatar-group.tsx | 0 src/components/{ => website}/testimonial/index.ts | 0 src/components/{ => website}/testimonial/star-rating.tsx | 0 src/components/{ => website}/testimonial/testimonials.tsx | 0 .../{ => website}/testimonial/user-trust-section.tsx | 0 33 files changed, 16 insertions(+), 15 deletions(-) rename src/components/{layout => shared}/library-logos.tsx (100%) rename src/components/{ => website}/banners/action-banner.tsx (100%) rename src/components/{ => website}/banners/compact-banner.tsx (100%) rename src/components/{ => website}/banners/confetti-banner.tsx (100%) rename src/components/{ => website}/banners/index.ts (100%) rename src/components/{ => website}/layout/background-patterns.tsx (100%) rename src/components/{ => website}/layout/call-to-action.tsx (100%) rename src/components/{ => website}/layout/footer/footer.tsx (96%) rename src/components/{ => website}/layout/footer/index.ts (100%) rename src/components/{ => website}/layout/header/header.tsx (95%) rename src/components/{ => website}/layout/header/index.ts (100%) rename src/components/{ => website}/layout/index.ts (82%) rename src/components/{ => website}/layout/page-hero.tsx (100%) rename src/components/{ => website}/navigation/custom-link.tsx (100%) rename src/components/{ => website}/navigation/footer-menu.tsx (100%) rename src/components/{ => website}/navigation/index.ts (100%) rename src/components/{ => website}/navigation/navbar.tsx (100%) rename src/components/{ => website}/newsletter/confirm-dialog.tsx (100%) rename src/components/{ => website}/newsletter/form.tsx (100%) rename src/components/{ => website}/newsletter/index.ts (100%) rename src/components/{ => website}/testimonial/avatar-group.tsx (100%) rename src/components/{ => website}/testimonial/index.ts (100%) rename src/components/{ => website}/testimonial/star-rating.tsx (100%) rename src/components/{ => website}/testimonial/testimonials.tsx (100%) rename src/components/{ => website}/testimonial/user-trust-section.tsx (100%) diff --git a/src/app/[locale]/(website)/about/page.tsx b/src/app/[locale]/(website)/about/page.tsx index 7b8dba5..9d2d124 100644 --- a/src/app/[locale]/(website)/about/page.tsx +++ b/src/app/[locale]/(website)/about/page.tsx @@ -2,7 +2,7 @@ import { Metadata } from 'next'; import { getTranslations } from 'next-intl/server'; import { Users, Target, Award, Heart, Globe, Zap, Shield, TrendingUp } from 'lucide-react'; import { generatePageMetadata } from '@/lib'; -import { CallToActionSection, PageHero } from '@/components/layout'; +import { CallToActionSection, PageHero } from '@/components/website/layout'; export async function generateMetadata(): Promise { const t = await getTranslations('about'); diff --git a/src/app/[locale]/(website)/contact/page.tsx b/src/app/[locale]/(website)/contact/page.tsx index bb8e7a4..66313e7 100644 --- a/src/app/[locale]/(website)/contact/page.tsx +++ b/src/app/[locale]/(website)/contact/page.tsx @@ -1,7 +1,7 @@ import { Metadata } from 'next'; import { getTranslations } from 'next-intl/server'; import { generatePageMetadata } from '@/lib'; -import { PageHero } from '@/components/layout'; +import { PageHero } from '@/components/website/layout'; import { ContactForm } from './components/contact-form'; import { ContactInfo } from './components/contact-info'; diff --git a/src/app/[locale]/(website)/home/components/hero.tsx b/src/app/[locale]/(website)/home/components/hero.tsx index d8315fb..61f07ca 100644 --- a/src/app/[locale]/(website)/home/components/hero.tsx +++ b/src/app/[locale]/(website)/home/components/hero.tsx @@ -1,8 +1,8 @@ import { GITHUB_URL, TESTIMONIALS as staticTestimonials } from '@/constants'; -import { CompactBanner } from '@/components/banners'; +import { CompactBanner } from '@/components/website/banners'; import { RandomText, TextRotator } from '@/components/shared'; -import { UserTrustSection } from '@/components/testimonial'; +import { UserTrustSection } from '@/components/website/testimonial'; import { useTranslations } from 'next-intl'; function getTestimonialData() { diff --git a/src/app/[locale]/(website)/layout.tsx b/src/app/[locale]/(website)/layout.tsx index 98a3832..5bd26d6 100644 --- a/src/app/[locale]/(website)/layout.tsx +++ b/src/app/[locale]/(website)/layout.tsx @@ -1,4 +1,4 @@ -import { Header, Footer } from '@/components/layout'; +import { Header, Footer } from '@/components/website/layout'; export default function WebSiteLayout({ children diff --git a/src/app/[locale]/(website)/page.tsx b/src/app/[locale]/(website)/page.tsx index 47e4788..b0dacfb 100644 --- a/src/app/[locale]/(website)/page.tsx +++ b/src/app/[locale]/(website)/page.tsx @@ -7,10 +7,11 @@ // console.info('User', user, isLoading); // } -import { BackgroundPatterns, LibraryLogos } from '@/components/layout'; -import { NewsletterConfirmDialog } from '@/components/newsletter'; -import { Testimonials } from '@/components/testimonial'; +import { BackgroundPatterns } from '@/components/website/layout'; +import { NewsletterConfirmDialog } from '@/components/website/newsletter'; +import { Testimonials } from '@/components/website/testimonial'; import { Hero, GitHubSection } from './home/components'; +import { LibraryLogos } from '@/components/shared'; export default function Page() { return ( diff --git a/src/app/[locale]/(website)/pricing/page.tsx b/src/app/[locale]/(website)/pricing/page.tsx index c3a200d..f680327 100644 --- a/src/app/[locale]/(website)/pricing/page.tsx +++ b/src/app/[locale]/(website)/pricing/page.tsx @@ -4,9 +4,9 @@ import { getTranslations } from 'next-intl/server'; import { generatePageMetadata } from '@/lib'; import PricingSection from './components/pricing-section'; import { PLANS } from '@/constants'; -import { ActionBanner } from '@/components/banners/action-banner'; +import { ActionBanner } from '@/components/website/banners/action-banner'; import { Building } from 'lucide-react'; -import { PageHero } from '@/components/layout'; +import { PageHero } from '@/components/website/layout'; export async function generateMetadata(): Promise { const t = await getTranslations('pricing'); diff --git a/src/app/[locale]/not-found.tsx b/src/app/[locale]/not-found.tsx index d5fdd63..048ea53 100644 --- a/src/app/[locale]/not-found.tsx +++ b/src/app/[locale]/not-found.tsx @@ -1,4 +1,4 @@ -import { BackgroundPatterns } from '@/components/layout'; +import { BackgroundPatterns } from '@/components/website/layout'; import { Link } from '@/lib/i18n/navigation'; import { getTranslations } from 'next-intl/server'; import Image from 'next/image'; diff --git a/src/components/shared/index.ts b/src/components/shared/index.ts index e17a58c..0055f58 100644 --- a/src/components/shared/index.ts +++ b/src/components/shared/index.ts @@ -9,3 +9,4 @@ export * from './small-loading'; export * from './text-rotator'; export * from './themed-image'; export * from './version'; +export * from './library-logos'; diff --git a/src/components/layout/library-logos.tsx b/src/components/shared/library-logos.tsx similarity index 100% rename from src/components/layout/library-logos.tsx rename to src/components/shared/library-logos.tsx diff --git a/src/components/banners/action-banner.tsx b/src/components/website/banners/action-banner.tsx similarity index 100% rename from src/components/banners/action-banner.tsx rename to src/components/website/banners/action-banner.tsx diff --git a/src/components/banners/compact-banner.tsx b/src/components/website/banners/compact-banner.tsx similarity index 100% rename from src/components/banners/compact-banner.tsx rename to src/components/website/banners/compact-banner.tsx diff --git a/src/components/banners/confetti-banner.tsx b/src/components/website/banners/confetti-banner.tsx similarity index 100% rename from src/components/banners/confetti-banner.tsx rename to src/components/website/banners/confetti-banner.tsx diff --git a/src/components/banners/index.ts b/src/components/website/banners/index.ts similarity index 100% rename from src/components/banners/index.ts rename to src/components/website/banners/index.ts diff --git a/src/components/layout/background-patterns.tsx b/src/components/website/layout/background-patterns.tsx similarity index 100% rename from src/components/layout/background-patterns.tsx rename to src/components/website/layout/background-patterns.tsx diff --git a/src/components/layout/call-to-action.tsx b/src/components/website/layout/call-to-action.tsx similarity index 100% rename from src/components/layout/call-to-action.tsx rename to src/components/website/layout/call-to-action.tsx diff --git a/src/components/layout/footer/footer.tsx b/src/components/website/layout/footer/footer.tsx similarity index 96% rename from src/components/layout/footer/footer.tsx rename to src/components/website/layout/footer/footer.tsx index 6ccc007..8ae41d0 100644 --- a/src/components/layout/footer/footer.tsx +++ b/src/components/website/layout/footer/footer.tsx @@ -7,9 +7,9 @@ import { CompactThemeSwitcher } from '@/components/switchers'; import { DevelopedBy, Logo, Version } from '@/components/shared'; import { SOCIAL_LINKS, FOOTER_LINKS } from '@/constants'; import { Separator } from '@radix-ui/themes'; -import { CompactNewsletter } from '@/components/newsletter'; +import { CompactNewsletter } from '@/components/website/newsletter'; import { useTranslations } from 'next-intl'; -import { FooterMenu } from '@/components/navigation'; +import { FooterMenu } from '@/components/website/navigation'; export function Footer() { const t = useTranslations(); diff --git a/src/components/layout/footer/index.ts b/src/components/website/layout/footer/index.ts similarity index 100% rename from src/components/layout/footer/index.ts rename to src/components/website/layout/footer/index.ts diff --git a/src/components/layout/header/header.tsx b/src/components/website/layout/header/header.tsx similarity index 95% rename from src/components/layout/header/header.tsx rename to src/components/website/layout/header/header.tsx index 01fcb42..4712851 100644 --- a/src/components/layout/header/header.tsx +++ b/src/components/website/layout/header/header.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Navbar } from '@/components/navigation'; +import { Navbar } from '@/components/website/navigation'; import { Logo } from '@/components/shared/'; import { useStickyNavbar } from '@/hooks'; import { CompactLocaleSwitcher } from '@/components/switchers'; diff --git a/src/components/layout/header/index.ts b/src/components/website/layout/header/index.ts similarity index 100% rename from src/components/layout/header/index.ts rename to src/components/website/layout/header/index.ts diff --git a/src/components/layout/index.ts b/src/components/website/layout/index.ts similarity index 82% rename from src/components/layout/index.ts rename to src/components/website/layout/index.ts index e86742d..f28e03e 100644 --- a/src/components/layout/index.ts +++ b/src/components/website/layout/index.ts @@ -3,4 +3,3 @@ export * from './footer'; export * from './background-patterns'; export * from './call-to-action'; export * from './page-hero'; -export * from './library-logos'; diff --git a/src/components/layout/page-hero.tsx b/src/components/website/layout/page-hero.tsx similarity index 100% rename from src/components/layout/page-hero.tsx rename to src/components/website/layout/page-hero.tsx diff --git a/src/components/navigation/custom-link.tsx b/src/components/website/navigation/custom-link.tsx similarity index 100% rename from src/components/navigation/custom-link.tsx rename to src/components/website/navigation/custom-link.tsx diff --git a/src/components/navigation/footer-menu.tsx b/src/components/website/navigation/footer-menu.tsx similarity index 100% rename from src/components/navigation/footer-menu.tsx rename to src/components/website/navigation/footer-menu.tsx diff --git a/src/components/navigation/index.ts b/src/components/website/navigation/index.ts similarity index 100% rename from src/components/navigation/index.ts rename to src/components/website/navigation/index.ts diff --git a/src/components/navigation/navbar.tsx b/src/components/website/navigation/navbar.tsx similarity index 100% rename from src/components/navigation/navbar.tsx rename to src/components/website/navigation/navbar.tsx diff --git a/src/components/newsletter/confirm-dialog.tsx b/src/components/website/newsletter/confirm-dialog.tsx similarity index 100% rename from src/components/newsletter/confirm-dialog.tsx rename to src/components/website/newsletter/confirm-dialog.tsx diff --git a/src/components/newsletter/form.tsx b/src/components/website/newsletter/form.tsx similarity index 100% rename from src/components/newsletter/form.tsx rename to src/components/website/newsletter/form.tsx diff --git a/src/components/newsletter/index.ts b/src/components/website/newsletter/index.ts similarity index 100% rename from src/components/newsletter/index.ts rename to src/components/website/newsletter/index.ts diff --git a/src/components/testimonial/avatar-group.tsx b/src/components/website/testimonial/avatar-group.tsx similarity index 100% rename from src/components/testimonial/avatar-group.tsx rename to src/components/website/testimonial/avatar-group.tsx diff --git a/src/components/testimonial/index.ts b/src/components/website/testimonial/index.ts similarity index 100% rename from src/components/testimonial/index.ts rename to src/components/website/testimonial/index.ts diff --git a/src/components/testimonial/star-rating.tsx b/src/components/website/testimonial/star-rating.tsx similarity index 100% rename from src/components/testimonial/star-rating.tsx rename to src/components/website/testimonial/star-rating.tsx diff --git a/src/components/testimonial/testimonials.tsx b/src/components/website/testimonial/testimonials.tsx similarity index 100% rename from src/components/testimonial/testimonials.tsx rename to src/components/website/testimonial/testimonials.tsx diff --git a/src/components/testimonial/user-trust-section.tsx b/src/components/website/testimonial/user-trust-section.tsx similarity index 100% rename from src/components/testimonial/user-trust-section.tsx rename to src/components/website/testimonial/user-trust-section.tsx From c1db1f6b0b4401129b91d94cb9445ba0e565f19f Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Tue, 14 Oct 2025 23:32:46 +0300 Subject: [PATCH 13/49] chore: Updates "@/components" alias Updates the "@/components" alias to "@/comp" in components.json. Refactors imports throughout the codebase to reflect the alias change, ensuring correct component resolution and project consistency. --- components.json | 2 +- src/components/auth/auth-link.tsx | 2 +- src/components/auth/sign-with-button.tsx | 4 ++-- src/components/shared/cookie-consent.tsx | 2 +- src/components/shared/developed-by.tsx | 2 +- src/components/shared/library-logos.tsx | 2 +- src/components/shared/logo.tsx | 2 +- src/components/shared/page-loading.tsx | 2 +- src/components/shared/small-loading.tsx | 2 +- src/components/shared/version.tsx | 2 +- .../switchers/locale/compact-locale-switcher.tsx | 4 ++-- src/components/switchers/locale/locale-switcher.tsx | 4 ++-- .../switchers/theme/compact-theme-switcher.tsx | 10 ++-------- .../switchers/theme/small-theme-switcher.tsx | 4 ++-- src/components/switchers/theme/theme-toggle.tsx | 2 +- src/components/website/layout/footer/footer.tsx | 10 +++++----- src/components/website/layout/header/header.tsx | 8 ++++---- src/components/website/navigation/navbar.tsx | 2 +- src/components/website/newsletter/confirm-dialog.tsx | 8 +------- src/components/website/newsletter/form.tsx | 2 +- src/components/website/testimonial/avatar-group.tsx | 2 +- src/components/website/testimonial/testimonials.tsx | 2 +- tsconfig.json | 2 +- 23 files changed, 35 insertions(+), 47 deletions(-) diff --git a/components.json b/components.json index 5aea8f5..9341783 100644 --- a/components.json +++ b/components.json @@ -15,7 +15,7 @@ "aliases": { "components": "@/components", "utils": "@/lib/utils", - "ui": "@/components/ui", + "ui": "@/comp/ui", "lib": "@/lib", "hooks": "@/hooks", "constants": "@/constants", diff --git a/src/components/auth/auth-link.tsx b/src/components/auth/auth-link.tsx index e64d2c4..e2a1d82 100644 --- a/src/components/auth/auth-link.tsx +++ b/src/components/auth/auth-link.tsx @@ -1,7 +1,7 @@ 'use client'; import { ReactNode } from 'react'; -import { Button, buttonVariants } from '@/components/ui'; +import { Button, buttonVariants } from '@/comp/ui'; import { cn } from '@/lib'; import { Link } from '@/lib/i18n/navigation'; diff --git a/src/components/auth/sign-with-button.tsx b/src/components/auth/sign-with-button.tsx index e0cb521..e7798fd 100644 --- a/src/components/auth/sign-with-button.tsx +++ b/src/components/auth/sign-with-button.tsx @@ -3,7 +3,7 @@ import { useTranslations } from 'next-intl'; import * as React from 'react'; -import { Button, buttonVariants } from '@/components/ui/button'; +import { Button, buttonVariants } from '@/comp/ui/button'; import { cn } from '@/lib'; import { Provider } from '@/lib/auth/providers'; import { @@ -15,7 +15,7 @@ import { FacebookIcon, TwitterIcon, LinkedinIcon -} from '@/components/icons'; +} from '@/comp/icons'; import { signIn } from '@/lib/auth/auth'; import { AUTH_ROUTES } from '@/lib/auth/constants'; diff --git a/src/components/shared/cookie-consent.tsx b/src/components/shared/cookie-consent.tsx index 8ae2d53..5f0cf90 100644 --- a/src/components/shared/cookie-consent.tsx +++ b/src/components/shared/cookie-consent.tsx @@ -15,7 +15,7 @@ import { SheetHeader, SheetTitle, SheetTrigger -} from '@/components/ui'; +} from '@/comp/ui'; import { Cookie, Settings, Shield, BarChart3, Target } from 'lucide-react'; import type { GtagConsentParams } from '@/types/gtag'; import { diff --git a/src/components/shared/developed-by.tsx b/src/components/shared/developed-by.tsx index daabddf..6652047 100644 --- a/src/components/shared/developed-by.tsx +++ b/src/components/shared/developed-by.tsx @@ -1,5 +1,5 @@ import Image from 'next/image'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/comp/ui'; export function DevelopedBy() { return ( diff --git a/src/components/shared/library-logos.tsx b/src/components/shared/library-logos.tsx index aa9ac94..a9bf66e 100644 --- a/src/components/shared/library-logos.tsx +++ b/src/components/shared/library-logos.tsx @@ -1,4 +1,4 @@ -import { ThemedImage } from '@/components/shared'; +import { ThemedImage } from '@/comp/shared'; import { useTranslations } from 'next-intl'; import React from 'react'; diff --git a/src/components/shared/logo.tsx b/src/components/shared/logo.tsx index d7e7230..8dc852a 100644 --- a/src/components/shared/logo.tsx +++ b/src/components/shared/logo.tsx @@ -1,6 +1,6 @@ import { useTranslations } from 'next-intl'; import { Link } from '@/lib/i18n/navigation'; -import { ThemedImage } from '@/components/shared'; +import { ThemedImage } from '@/comp/shared'; export function Logo({ size = 40, diff --git a/src/components/shared/page-loading.tsx b/src/components/shared/page-loading.tsx index fc37581..958f0d7 100644 --- a/src/components/shared/page-loading.tsx +++ b/src/components/shared/page-loading.tsx @@ -1,6 +1,6 @@ import { getTranslations } from 'next-intl/server'; -import { Loading } from '@/components/shared'; +import { Loading } from '@/comp/shared'; import { cn } from '@/lib'; interface Props { diff --git a/src/components/shared/small-loading.tsx b/src/components/shared/small-loading.tsx index e6e12b0..bed234c 100644 --- a/src/components/shared/small-loading.tsx +++ b/src/components/shared/small-loading.tsx @@ -2,7 +2,7 @@ import { Loader } from 'lucide-react'; -import { Button } from '@/components/ui'; +import { Button } from '@/comp/ui'; export function SmallLoading() { return ( diff --git a/src/components/shared/version.tsx b/src/components/shared/version.tsx index 21a0bca..4785c8d 100644 --- a/src/components/shared/version.tsx +++ b/src/components/shared/version.tsx @@ -1,4 +1,4 @@ -import { Badge } from '@/components/ui'; +import { Badge } from '@/comp/ui'; import packageJson from '../../../package.json'; export function Version() { diff --git a/src/components/switchers/locale/compact-locale-switcher.tsx b/src/components/switchers/locale/compact-locale-switcher.tsx index 66e1db2..4c74ba6 100644 --- a/src/components/switchers/locale/compact-locale-switcher.tsx +++ b/src/components/switchers/locale/compact-locale-switcher.tsx @@ -4,8 +4,8 @@ import { Check, ChevronDown } from 'lucide-react'; import Image from 'next/image'; import { Suspense, useEffect, useState } from 'react'; import { useLocale } from 'next-intl'; -import { SmallLoading } from '@/components/shared'; -import { Button, Popover, PopoverContent, PopoverTrigger } from '@/components/ui'; +import { SmallLoading } from '@/comp/shared'; +import { Button, Popover, PopoverContent, PopoverTrigger } from '@/comp/ui'; import { usePathname, useRouter } from '@/lib/i18n/navigation'; import { cn, LOCALES_WITH_FLAG } from '@/lib'; import { Locale } from '@/constants'; diff --git a/src/components/switchers/locale/locale-switcher.tsx b/src/components/switchers/locale/locale-switcher.tsx index b31c5b1..35dd3b9 100644 --- a/src/components/switchers/locale/locale-switcher.tsx +++ b/src/components/switchers/locale/locale-switcher.tsx @@ -4,8 +4,8 @@ import { Globe } from 'lucide-react'; import Image from 'next/image'; import { Suspense, useEffect, useState } from 'react'; -import { SmallLoading } from '@/components/shared'; -import { Button, Popover, PopoverContent, PopoverTrigger } from '@/components/ui'; +import { SmallLoading } from '@/comp/shared'; +import { Button, Popover, PopoverContent, PopoverTrigger } from '@/comp/ui'; import { Locale } from '@/constants'; import { usePathname, useRouter } from '@/lib/i18n/navigation'; import { LOCALES_WITH_FLAG } from '@/lib/utils'; diff --git a/src/components/switchers/theme/compact-theme-switcher.tsx b/src/components/switchers/theme/compact-theme-switcher.tsx index 4375cf3..c4fdc7d 100644 --- a/src/components/switchers/theme/compact-theme-switcher.tsx +++ b/src/components/switchers/theme/compact-theme-switcher.tsx @@ -2,14 +2,8 @@ import { MoonIcon, SunIcon, MonitorIcon } from 'lucide-react'; import { Suspense } from 'react'; -import { SmallLoading } from '@/components/shared'; -import { - ToggleGroup, - ToggleGroupItem, - Tooltip, - TooltipContent, - TooltipTrigger -} from '@/components/ui'; +import { SmallLoading } from '@/comp/shared'; +import { ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipTrigger } from '@/comp/ui'; import { useNextTheme } from '@/hooks'; export const CompactThemeSwitcher = () => { diff --git a/src/components/switchers/theme/small-theme-switcher.tsx b/src/components/switchers/theme/small-theme-switcher.tsx index 4e1df93..a8a8572 100644 --- a/src/components/switchers/theme/small-theme-switcher.tsx +++ b/src/components/switchers/theme/small-theme-switcher.tsx @@ -2,8 +2,8 @@ import { MoonIcon, SunIcon } from 'lucide-react'; import { useId } from 'react'; -import { SmallLoading } from '@/components/shared'; -import { Switch, Label } from '@/components/ui'; +import { SmallLoading } from '@/comp/shared'; +import { Switch, Label } from '@/comp/ui'; import { useNextTheme } from '@/hooks'; export const SmallThemeSwitcher = () => { diff --git a/src/components/switchers/theme/theme-toggle.tsx b/src/components/switchers/theme/theme-toggle.tsx index 56c5224..98dbb2d 100644 --- a/src/components/switchers/theme/theme-toggle.tsx +++ b/src/components/switchers/theme/theme-toggle.tsx @@ -4,7 +4,7 @@ import { Moon, Sun } from 'lucide-react'; import { useTheme } from 'next-themes'; import * as React from 'react'; -import { Button } from '@/components/ui'; +import { Button } from '@/comp/ui'; export function ThemeToggle() { const { theme, setTheme } = useTheme(); diff --git a/src/components/website/layout/footer/footer.tsx b/src/components/website/layout/footer/footer.tsx index 8ae41d0..77e5040 100644 --- a/src/components/website/layout/footer/footer.tsx +++ b/src/components/website/layout/footer/footer.tsx @@ -1,15 +1,15 @@ 'use client'; import React from 'react'; -import { Button } from '@/components/ui'; +import { Button } from '@/comp/ui'; import { Heart, Palette } from 'lucide-react'; -import { CompactThemeSwitcher } from '@/components/switchers'; -import { DevelopedBy, Logo, Version } from '@/components/shared'; +import { CompactThemeSwitcher } from '@/comp/switchers'; +import { DevelopedBy, Logo, Version } from '@/comp/shared'; import { SOCIAL_LINKS, FOOTER_LINKS } from '@/constants'; import { Separator } from '@radix-ui/themes'; -import { CompactNewsletter } from '@/components/website/newsletter'; +import { CompactNewsletter } from '@/comp/website/newsletter'; import { useTranslations } from 'next-intl'; -import { FooterMenu } from '@/components/website/navigation'; +import { FooterMenu } from '@/comp/website/navigation'; export function Footer() { const t = useTranslations(); diff --git a/src/components/website/layout/header/header.tsx b/src/components/website/layout/header/header.tsx index 4712851..b2cfb7a 100644 --- a/src/components/website/layout/header/header.tsx +++ b/src/components/website/layout/header/header.tsx @@ -1,10 +1,10 @@ 'use client'; -import { Navbar } from '@/components/website/navigation'; -import { Logo } from '@/components/shared/'; +import { Navbar } from '@/comp/website/navigation'; +import { Logo } from '@/comp/shared/'; import { useStickyNavbar } from '@/hooks'; -import { CompactLocaleSwitcher } from '@/components/switchers'; -import { SignInButton, SignUpButton } from '@/components/auth'; +import { CompactLocaleSwitcher } from '@/comp/switchers'; +import { SignInButton, SignUpButton } from '@/comp/auth'; export function Header() { const sticky = useStickyNavbar(); diff --git a/src/components/website/navigation/navbar.tsx b/src/components/website/navigation/navbar.tsx index 21f3586..795151f 100644 --- a/src/components/website/navigation/navbar.tsx +++ b/src/components/website/navigation/navbar.tsx @@ -3,7 +3,7 @@ import { useTranslations } from 'next-intl'; import { Link, usePathname } from '@/lib/i18n/navigation'; -import { NavigationMenu, NavigationMenuItem, NavigationMenuList } from '@/components/ui'; +import { NavigationMenu, NavigationMenuItem, NavigationMenuList } from '@/comp/ui'; import { cn } from '@/lib'; import { NAV_LINKS } from '@/constants'; diff --git a/src/components/website/newsletter/confirm-dialog.tsx b/src/components/website/newsletter/confirm-dialog.tsx index 8e831a7..011853e 100644 --- a/src/components/website/newsletter/confirm-dialog.tsx +++ b/src/components/website/newsletter/confirm-dialog.tsx @@ -1,10 +1,4 @@ -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription -} from '@/components/ui'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/comp/ui'; import { CheckCircle2, XCircle } from 'lucide-react'; import React from 'react'; import { useTranslations } from 'next-intl'; diff --git a/src/components/website/newsletter/form.tsx b/src/components/website/newsletter/form.tsx index ffbb67e..9d5d535 100644 --- a/src/components/website/newsletter/form.tsx +++ b/src/components/website/newsletter/form.tsx @@ -1,7 +1,7 @@ 'use client'; import React, { useState } from 'react'; -import { Button, Input } from '@/components/ui'; +import { Button, Input } from '@/comp/ui'; import { Heart, Mail, Send } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { Link } from '@/lib/i18n/navigation'; diff --git a/src/components/website/testimonial/avatar-group.tsx b/src/components/website/testimonial/avatar-group.tsx index a00348b..9195935 100644 --- a/src/components/website/testimonial/avatar-group.tsx +++ b/src/components/website/testimonial/avatar-group.tsx @@ -1,6 +1,6 @@ import Image from 'next/image'; import React from 'react'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/comp/ui'; interface AvatarGroupProps { users: { id: string; name: string; title: string; company: string; avatarUrl: string }[]; diff --git a/src/components/website/testimonial/testimonials.tsx b/src/components/website/testimonial/testimonials.tsx index 5b94f43..9d6215d 100644 --- a/src/components/website/testimonial/testimonials.tsx +++ b/src/components/website/testimonial/testimonials.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui'; +import { Avatar, AvatarFallback, AvatarImage } from '@/comp/ui'; import { Star } from 'lucide-react'; import { TESTIMONIALS } from '@/constants'; import { useTranslations } from 'next-intl'; diff --git a/tsconfig.json b/tsconfig.json index b8788ba..b42ad50 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,7 +28,7 @@ } ], "paths": { - "@/components/*": [ + "@/comp/*": [ "./src/components/*" ], "@/ui/*": [ From 547ebf95ec1789d74bab05363e39bfbd7c3ac4fd Mon Sep 17 00:00:00 2001 From: Mustafa Genc Date: Wed, 15 Oct 2025 23:52:33 +0300 Subject: [PATCH 14/49] feat: Introduces authentication UI Adds a new user interface for authentication. This includes: - Login and registration pages with supporting components. - UI components for form elements, cards, and social sign-in. - Radix UI separator dependency. - Layout for auth pages. --- package.json | 1 + pnpm-lock.yaml | 3 + .../[locale]/(auth)/components/form-card.tsx | 22 ++ .../(auth)/components/sign-with-buttons.tsx | 13 + src/app/[locale]/(auth)/layout.tsx | 11 +- src/app/[locale]/(auth)/login/page.tsx | 41 ++- src/app/[locale]/(auth)/register/page.tsx | 47 +++- src/components/auth/sign-with-button.tsx | 2 - src/components/ui/button.tsx | 10 +- src/components/ui/card.tsx | 4 +- src/components/ui/empty.tsx | 94 +++++++ src/components/ui/field.tsx | 237 ++++++++++++++++++ src/components/ui/index.ts | 9 + src/components/ui/input-group.tsx | 159 ++++++++++++ src/components/ui/input.tsx | 2 +- src/components/ui/item.tsx | 182 ++++++++++++++ src/components/ui/kbd.tsx | 28 +++ src/components/ui/label.tsx | 2 +- src/components/ui/separator.tsx | 28 +++ src/components/ui/spinner.tsx | 16 ++ src/components/ui/textarea.tsx | 2 +- 21 files changed, 896 insertions(+), 17 deletions(-) create mode 100644 src/app/[locale]/(auth)/components/form-card.tsx create mode 100644 src/app/[locale]/(auth)/components/sign-with-buttons.tsx create mode 100644 src/components/ui/empty.tsx create mode 100644 src/components/ui/field.tsx create mode 100644 src/components/ui/input-group.tsx create mode 100644 src/components/ui/item.tsx create mode 100644 src/components/ui/kbd.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/spinner.tsx diff --git a/package.json b/package.json index 6b48eb4..597a474 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "@radix-ui/react-radio-group": "^1.3.8", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slider": "^1.3.6", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e700af4..521cf01 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: '@radix-ui/react-select': specifier: ^2.2.6 version: 2.2.6(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-separator': + specifier: ^1.1.7 + version: 1.1.7(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-slider': specifier: ^1.3.6 version: 1.3.6(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) diff --git a/src/app/[locale]/(auth)/components/form-card.tsx b/src/app/[locale]/(auth)/components/form-card.tsx new file mode 100644 index 0000000..f4954a4 --- /dev/null +++ b/src/app/[locale]/(auth)/components/form-card.tsx @@ -0,0 +1,22 @@ +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/comp/ui'; +import React from 'react'; + +interface FormCardProps { + children: React.ReactNode; + title: string; + description: string; + footer?: React.ReactNode; +} + +export function FormCard({ children, title, description, footer }: FormCardProps) { + return ( + + + {title} + {description} + + {children} + {footer} + + ); +} diff --git a/src/app/[locale]/(auth)/components/sign-with-buttons.tsx b/src/app/[locale]/(auth)/components/sign-with-buttons.tsx new file mode 100644 index 0000000..ed4bd19 --- /dev/null +++ b/src/app/[locale]/(auth)/components/sign-with-buttons.tsx @@ -0,0 +1,13 @@ +import { SignWithButton } from '@/comp/auth/sign-with-button'; + +export function SignWithButtons() { + return ( +
+ + + + + +
+ ); +} diff --git a/src/app/[locale]/(auth)/layout.tsx b/src/app/[locale]/(auth)/layout.tsx index 50fb2e3..984227d 100644 --- a/src/app/[locale]/(auth)/layout.tsx +++ b/src/app/[locale]/(auth)/layout.tsx @@ -3,5 +3,14 @@ export default function AuthLayout({ }: Readonly<{ children: React.ReactNode; }>) { - return <>{children}; + return ( +
+
+
{children}
+
+
+ Test +
+
+ ); } diff --git a/src/app/[locale]/(auth)/login/page.tsx b/src/app/[locale]/(auth)/login/page.tsx index 0f49ebc..7d549c7 100644 --- a/src/app/[locale]/(auth)/login/page.tsx +++ b/src/app/[locale]/(auth)/login/page.tsx @@ -1,5 +1,42 @@ -'use client'; +import { Button, Input, Label } from '@/comp/ui'; +import { SignWithButtons } from '../components/sign-with-buttons'; +import { FormCard } from '../components/form-card'; export default function Page() { - return <>Page; + return ( + + {' '} + + + + } + > +
+
+
+ + +
+
+ + +
+
+
+
+ ); } diff --git a/src/app/[locale]/(auth)/register/page.tsx b/src/app/[locale]/(auth)/register/page.tsx index 0f49ebc..3ec6370 100644 --- a/src/app/[locale]/(auth)/register/page.tsx +++ b/src/app/[locale]/(auth)/register/page.tsx @@ -1,5 +1,48 @@ -'use client'; +import { Button, Field, FieldDescription, FieldGroup, FieldLabel, Input } from '@/comp/ui'; +import { SignWithButtons } from '../components/sign-with-buttons'; +import { FormCard } from '../components/form-card'; export default function Page() { - return <>Page; + return ( + + {' '} + + + + } + > +
+ + + Full Name + + + + Email + + + We'll use this to contact you. We will not share your email with + anyone else. + + + + Password + + Must be at least 8 characters long. + + + Confirm Password + + Please confirm your password. + + +
+
+ ); } diff --git a/src/components/auth/sign-with-button.tsx b/src/components/auth/sign-with-button.tsx index e7798fd..1a2057c 100644 --- a/src/components/auth/sign-with-button.tsx +++ b/src/components/auth/sign-with-button.tsx @@ -1,5 +1,3 @@ -'use client'; - import { useTranslations } from 'next-intl'; import * as React from 'react'; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 21fc788..17144b4 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -2,19 +2,19 @@ import * as React from 'react'; import { Slot } from '@radix-ui/react-slot'; import { cva, type VariantProps } from 'class-variance-authority'; -import { cn } from '@/lib/utils'; +import { cn } from '@/lib/utils/index'; const buttonVariants = cva( 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer', { variants: { variant: { - default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90', destructive: - 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', + 'bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90', outline: - 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', - secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', + 'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'text-primary underline-offset-4 hover:underline' }, diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index cfcf2b7..9c0b9df 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; -import { cn } from '@/lib/utils'; +import { cn } from '@/lib/utils/index'; const Card = React.forwardRef>( ({ className, ...props }, ref) => (
) diff --git a/src/components/ui/empty.tsx b/src/components/ui/empty.tsx new file mode 100644 index 0000000..c5147bb --- /dev/null +++ b/src/components/ui/empty.tsx @@ -0,0 +1,94 @@ +import { cva, type VariantProps } from 'class-variance-authority'; + +import { cn } from '@/lib/utils/index'; + +function Empty({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function EmptyHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +const emptyMediaVariants = cva( + 'mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0', + { + variants: { + variant: { + default: 'bg-transparent', + icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6" + } + }, + defaultVariants: { + variant: 'default' + } + } +); + +function EmptyMedia({ + className, + variant = 'default', + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
+ ); +} + +function EmptyTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) { + return ( +
a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4', + className + )} + {...props} + /> + ); +} + +function EmptyContent({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia }; diff --git a/src/components/ui/field.tsx b/src/components/ui/field.tsx new file mode 100644 index 0000000..4f87ae8 --- /dev/null +++ b/src/components/ui/field.tsx @@ -0,0 +1,237 @@ +'use client'; + +import { useMemo } from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; + +import { cn } from '@/lib/utils/index'; +import { Label } from '@/comp/ui/label'; +import { Separator } from '@/comp/ui/separator'; + +function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) { + return ( +
[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3', + className + )} + {...props} + /> + ); +} + +function FieldLegend({ + className, + variant = 'legend', + ...props +}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) { + return ( + + ); +} + +function FieldGroup({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
[data-slot=field-group]]:gap-4', + className + )} + {...props} + /> + ); +} + +const fieldVariants = cva('group/field data-[invalid=true]:text-destructive flex w-full gap-3', { + variants: { + orientation: { + vertical: ['flex-col [&>*]:w-full [&>.sr-only]:w-auto'], + horizontal: [ + 'flex-row items-center', + '[&>[data-slot=field-label]]:flex-auto', + 'has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start' + ], + responsive: [ + '@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto', + '@md/field-group:[&>[data-slot=field-label]]:flex-auto', + '@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px' + ] + } + }, + defaultVariants: { + orientation: 'vertical' + } +}); + +function Field({ + className, + orientation = 'vertical', + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
+ ); +} + +function FieldContent({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function FieldLabel({ className, ...props }: React.ComponentProps) { + return ( +