From 2a8bda1beb700cb2e5f79af2c807d5e39a34ca27 Mon Sep 17 00:00:00 2001 From: yashmakhija Date: Wed, 12 Feb 2025 00:11:55 +0530 Subject: [PATCH 1/4] add: integrated user auth on frontend --- apps/user-fe/api/auth.ts | 57 +++++++ apps/user-fe/api/client.ts | 39 +++++ apps/user-fe/api/episodes.ts | 33 ++++ apps/user-fe/api/events.ts | 35 ++++ apps/user-fe/api/index.ts | 5 + apps/user-fe/api/types/index.ts | 18 +++ apps/user-fe/app/_components/navbar.tsx | 38 ++++- .../app/_components/signup/otp-dialog.tsx | 153 +++++++++++++----- .../app/_components/signup/profile.tsx | 95 +++++++++++ .../app/_components/signup/singupDialog.tsx | 32 ++-- 10 files changed, 434 insertions(+), 71 deletions(-) create mode 100644 apps/user-fe/api/auth.ts create mode 100644 apps/user-fe/api/client.ts create mode 100644 apps/user-fe/api/episodes.ts create mode 100644 apps/user-fe/api/events.ts create mode 100644 apps/user-fe/api/index.ts create mode 100644 apps/user-fe/api/types/index.ts create mode 100644 apps/user-fe/app/_components/signup/profile.tsx diff --git a/apps/user-fe/api/auth.ts b/apps/user-fe/api/auth.ts new file mode 100644 index 0000000..ecf0feb --- /dev/null +++ b/apps/user-fe/api/auth.ts @@ -0,0 +1,57 @@ +// Authentication related API calls +import { apiClient } from "./client"; + +interface LoginCredentials { + email: string; + password: string; +} + +interface SignupData { + email: string; + password: string; + name: string; +} + +interface OtpVerificationParams { + useremail: string; + otp: string; + device_id: string; + mydeviceid?: string; + mydeviceid2?: string; +} + +export const authApi = { + login: async (credentials: LoginCredentials) => { + const response = await apiClient.post("/auth/login", credentials); + return response.data; + }, + + signup: async (data: SignupData) => { + const response = await apiClient.post("/auth/signup", data); + return response.data; + }, + + logout: async () => { + const response = await apiClient.post("/auth/logout"); + return response.data; + }, + + verifyToken: async () => { + const response = await apiClient.get("/auth/verify"); + return response.data; + }, + + sendOtp: async (phone: string) => { + const response = await apiClient.get(`/get/sendotp`, { + params: { phone }, + }); + return response.data; + }, + + verifyOtp: async (params: OtpVerificationParams) => { + const response = await apiClient.get(`/get/otpverify`, { + params: params, + }); + return response.data; + }, +}; diff --git a/apps/user-fe/api/client.ts b/apps/user-fe/api/client.ts new file mode 100644 index 0000000..25aa66c --- /dev/null +++ b/apps/user-fe/api/client.ts @@ -0,0 +1,39 @@ +// Base API client setup +import axios from "axios"; + +const API_BASE_URL = + process.env.NEXT_PUBLIC_API_URL || "https://indiasgotlatentapi.akamai.net.in"; + +export const apiClient = axios.create({ + baseURL: API_BASE_URL, + headers: { + "Auth-Key": "appxapi", + }, +}); + +// Request interceptor +apiClient.interceptors.request.use( + (config) => { + const token = localStorage.getItem("token"); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +// Response interceptor +apiClient.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + // Handle unauthorized access + localStorage.removeItem("token"); + window.location.href = "/login"; + } + return Promise.reject(error); + } +); diff --git a/apps/user-fe/api/episodes.ts b/apps/user-fe/api/episodes.ts new file mode 100644 index 0000000..3ba69e0 --- /dev/null +++ b/apps/user-fe/api/episodes.ts @@ -0,0 +1,33 @@ +// Episodes related API calls +import { apiClient } from './client'; +import { PaginatedResponse } from './types'; + +export interface Episode { + id: string; + title: string; + thumbnail: string; + episodeNumber: number; + youtubeId: string; + isPremium: boolean; +} + +export const episodesApi = { + getAll: async (page = 1, limit = 10) => { + const response = await apiClient.get>('/episodes', { + params: { page, limit }, + }); + return response.data; + }, + + getById: async (id: string) => { + const response = await apiClient.get(`/episodes/${id}`); + return response.data; + }, + + getPremiumEpisodes: async (page = 1, limit = 10) => { + const response = await apiClient.get>('/episodes/premium', { + params: { page, limit }, + }); + return response.data; + }, +}; \ No newline at end of file diff --git a/apps/user-fe/api/events.ts b/apps/user-fe/api/events.ts new file mode 100644 index 0000000..9a6cc7c --- /dev/null +++ b/apps/user-fe/api/events.ts @@ -0,0 +1,35 @@ +// Events related API calls +import { apiClient } from './client'; +import { PaginatedResponse } from './types'; + +export interface Event { + id: string; + eventName: string; + date: string; + time: string; + venue: string; + location: string; + eventType: string; + ageRating: string; + reviews: number; + ticketPrice: number; +} + +export const eventsApi = { + getAll: async (page = 1, limit = 10) => { + const response = await apiClient.get>('/events', { + params: { page, limit }, + }); + return response.data; + }, + + getById: async (id: string) => { + const response = await apiClient.get(`/events/${id}`); + return response.data; + }, + + bookTicket: async (eventId: string, quantity: number) => { + const response = await apiClient.post(`/events/${eventId}/book`, { quantity }); + return response.data; + }, +}; \ No newline at end of file diff --git a/apps/user-fe/api/index.ts b/apps/user-fe/api/index.ts new file mode 100644 index 0000000..e085ac6 --- /dev/null +++ b/apps/user-fe/api/index.ts @@ -0,0 +1,5 @@ +// Export all API modules +export * from './auth'; +export * from './episodes'; +export * from './events'; +export * from './types'; \ No newline at end of file diff --git a/apps/user-fe/api/types/index.ts b/apps/user-fe/api/types/index.ts new file mode 100644 index 0000000..cedec06 --- /dev/null +++ b/apps/user-fe/api/types/index.ts @@ -0,0 +1,18 @@ +// Common types for API requests and responses +export interface ApiResponse { + data: T; + message: string; + status: number; +} + +export interface PaginatedResponse { + data: T[]; + total: number; + page: number; + limit: number; +} + +export interface ErrorResponse { + message: string; + status: number; +} diff --git a/apps/user-fe/app/_components/navbar.tsx b/apps/user-fe/app/_components/navbar.tsx index fa59f7f..973bdca 100644 --- a/apps/user-fe/app/_components/navbar.tsx +++ b/apps/user-fe/app/_components/navbar.tsx @@ -1,8 +1,9 @@ "use client"; import Link from "next/link"; import Image from "next/image"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { LoginDialog } from "./signup/singupDialog"; +import { Profile } from "./signup/profile"; import { Button } from "@repo/ui/button"; import { IMAGES } from "../_assets"; import { MenuIcon, XIcon } from "lucide-react"; @@ -11,6 +12,24 @@ import { AnimatedBackground } from "./animatedBackground"; export default function Navbar() { const [isLoginOpen, setIsLoginOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const [isLoggedIn, setIsLoggedIn] = useState(false); + + useEffect(() => { + // Check authentication status on mount and token change + const checkAuth = () => { + const token = localStorage.getItem("token"); + setIsLoggedIn(!!token); + }; + + checkAuth(); + + // Listen for storage changes (in case of logout from another tab) + window.addEventListener("storage", checkAuth); + + return () => { + window.removeEventListener("storage", checkAuth); + }; + }, []); return (