A modern Next.js application with Supabase authentication, featuring Google OAuth and email/password authentication with a clean, responsive UI.
-
Authentication System
- Google OAuth integration
- Email/password sign-up and sign-in
- Secure session management
- User profile synchronization with database
-
Modern UI/UX
- Responsive design with Tailwind CSS
- Custom UI components with Radix UI
- Loading states and error handling
- Clean, professional interface
-
State Management
- Zustand for global state management
- Real-time authentication state updates
- Persistent user sessions
- Frontend: Next.js 16 (App Router), React 19, TypeScript
- Styling: Tailwind CSS 4, Radix UI components
- Authentication: Supabase Auth
- State Management: Zustand
- Icons: Lucide React
- Build Tool: Turbopack
βββ app/ # Next.js App Router
β βββ SignIn/ # Sign-in page components
β β βββ SignIn.tsx # Main sign-in page layout
β βββ SignUp/ # Sign-up page components
β β βββ page.tsx # Sign-up page
βββ assets/ # Static assets
β βββ images/ # Image assets
β βββ index.ts # Image exports
βββ components/ # Reusable UI components
β βββ ui/ # Base UI components
β βββ index.ts # UI exports
βββ lib/ # Utility libraries
β βββ supabase.ts # Supabase client configuration
β βββ utils.ts # Utility functions (cn helper)
βββ Services/ # Business logic services
β βββ Auth.ts # Authentication service with Zustand
βββ Templates/ # Page templates
β βββ SignIn.tsx # Sign-in form template
β βββ SignUp.tsx # Sign-up form template
- Zustand Store: Manages global authentication state
- Supabase Integration: Handles OAuth and email/password auth
- User Synchronization: Syncs user data with database
- Session Management: Real-time auth state changes
- Button: Customizable button with multiple variants
- Field: Form field components with labels and validation
- Input: Styled input component
- Loading: Spinner component for loading states
- SignIn: Complete sign-in form with Google OAuth
- SignUp: Registration form with validation
- Node.js 18+
- npm/yarn/pnpm
- Supabase account
-
Clone the repository
git clone <repository-url> cd aidatatry
-
Install dependencies
npm install # or yarn install # or pnpm install
-
Environment Setup Create a
.env.localfile in the root directory:NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
-
Run the development server
npm run dev # or yarn dev # or pnpm dev
-
Open your browser Navigate to http://localhost:3000
npm run dev- Start development server with Turbopacknpm run build- Build the application for productionnpm run start- Start the production servernpm run lint- Run ESLint
Use type instead of interface
// good
interface Result {}
// better
type Result = {};Use appropriate types instead of any
// bad
type Result = {
auth: any;
};
// good
type Result = {
auth: unknown;
};
// better
type Result = {
auth: {token: string};
};Strict comparison
// bad
const isSame = a == b;
// good
const isSame = a === b;De-structuring on scope level
// bad
export default function Button({type, name, onClick}: ButtonProps) {}
// good
export default function Button(props: ButtonProps) {
const {type, name, onClick} = props;
}Use array functions instead of loops
// good
let sum = 0;
for (let i = 0; i < 10; i++) {
sum += i;
}
// better
const sum = Array(10)
.fill('')
.reduce((acc, index) => {
return acc + index;
}, 0);Use async/await instead of promises
// good
const getPosts = () => {
return fetch('/posts')
.then(response => response.json())
.catch();
};
// better
const getPosts = async () => {
try {
const request = await fetch('/posts');
const data = await request.json();
return data;
} catch {
// error logic
}
};Use fully-qualified names
// good
const getIdx = () => {
return 0;
};
const getMsg = () => {};
// better
const getIdNumber = () => {
return 0;
};
const getMessage = () => {};Fix warnings, never bypass ESLint
// bad
const isLoggedIn = useMemo(() => {
return false;
}, []);
useEffect(() => {
if (!isLoggedIn) {
// logic
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// good
const isLoggedIn = useMemo(() => {
return false;
}, []);
useEffect(() => {
if (!isLoggedIn) {
// logic
}
}, [isLoggedIn]);Use named imports
// bad
import * as lodash from 'lodash';
const name = lodash.capitalize('name');
// good
import {capitalize} from 'lodash';
const name = capitalize('name');Always assume null/undefined
// bad
const firstName = result.name.data.firstName;
// good
const firstName = result.data?.name?.firstName;
// better
import {get} from 'lodash';
const firstName = get(result, ['data', 'name', 'firstName'], '');Negate when short-circuiting
// bad
const variable = 0;
const result = variable && <View>Hello</View>;
// good
return variable ? <View>Hello</View> : null;
// preferred
return !!variable && <View>Hello</View>;Never add inline events
// bad
<Pressable
onPress={() => {
// logic
}}>
Action
</Pressable>;
// good
const onPress = () => {
// logic
};
<Pressable onPress={onPress}>Action</Pressable>;Never add inline styles
// bad
<View style={{position: "absolute"}}>
// contents
</View>
// good
<View style={styles.absoluteView}>
// contents
</View>Pass props by importance
// bad
<FormInput
onChangeText={() => {}}
value={true}
name={''}
onFocus={() => {}}
onBlur={() => {}}
readOnly={true}
/>
// good
<FormInput
value={true}
name={''}
readOnly={true}
onChangeText={() => {}}
onFocus={() => {}}
onBlur={() => {}}
/>Follow CSS property ordering
// bad
const styles = {
absoluteView: {
top: 0,
flex: 1,
position: 'absolute',
flexDirection: 'column',
},
};
// good
const styles = {
absoluteView: {
flex: 1,
flexDirection: 'column',
position: 'absolute',
top: 0,
},
};The project uses Tailwind CSS 4 with custom configuration for:
- Component variants
- Dark mode support
- Custom color schemes
next: Next.js frameworkreact: React library@supabase/supabase-js: Supabase clientzustand: State managementtailwindcss: CSS framework
@radix-ui/*: Accessible UI primitiveslucide-react: Icon libraryclass-variance-authority: Component variants