diff --git a/web_services/management_frontend/src/apicalls/api-fetcher.ts b/web_services/management_frontend/src/apicalls/api-fetcher.ts new file mode 100644 index 0000000..a37c7a8 --- /dev/null +++ b/web_services/management_frontend/src/apicalls/api-fetcher.ts @@ -0,0 +1,177 @@ +"use server"; +import { retrieveAccessToken } from "@/apicalls/mutual/cookies/token"; +import { + DEFAULT_RESPONSE, + defaultHeaders, + FetchOptions, + HttpMethod, + ApiResponse, + DEFAULT_TIMEOUT, +} from "./basics"; + +/** + * Creates a promise that rejects after a specified timeout + * @param ms Timeout in milliseconds + * @param controller AbortController to abort the fetch request + * @returns A promise that rejects after the timeout + */ +const createTimeoutPromise = ( + ms: number, + controller: AbortController +): Promise => { + return new Promise((_, reject) => { + setTimeout(() => { + controller.abort(); + reject(new Error(`Request timed out after ${ms}ms`)); + }, ms); + }); +}; + +/** + * Prepares a standardized API response + * @param response The response data + * @param statusCode HTTP status code + * @returns Standardized API response + */ +const prepareResponse = ( + response: T, + statusCode: number +): ApiResponse => { + try { + return { + status: statusCode, + data: response || ({} as T), + }; + } catch (error) { + console.error("Error preparing response:", error); + return { + ...DEFAULT_RESPONSE, + error: "Response parsing error", + } as ApiResponse; + } +}; + +/** + * Core fetch function with timeout and error handling + * @param url The URL to fetch + * @param options Fetch options + * @param headers Request headers + * @param payload Request payload + * @returns API response + */ +async function coreFetch( + url: string, + options: FetchOptions = {}, + headers: Record = defaultHeaders, + payload?: any +): Promise> { + const { method = "POST", cache = false, timeout = DEFAULT_TIMEOUT } = options; + + try { + const controller = new AbortController(); + const signal = controller.signal; + + const fetchOptions: RequestInit = { + method, + headers, + cache: cache ? "force-cache" : "no-cache", + signal, + }; + + // Add body if needed + if (method !== "GET" && payload) { + fetchOptions.body = JSON.stringify( + // Handle special case for updateDataWithToken + payload.payload ? payload.payload : payload + ); + } + + // Create timeout promise + const timeoutPromise = createTimeoutPromise(timeout, controller); + + // Race between fetch and timeout + const response = (await Promise.race([ + fetch(url, fetchOptions), + timeoutPromise, + ])) as Response; + + const responseJson = await response.json(); + + if (process.env.NODE_ENV !== "production") { + console.log("Fetching:", url, fetchOptions); + console.log("Response:", responseJson); + } + + return prepareResponse(responseJson, response.status); + } catch (error) { + console.error(`Fetch error (${url}):`, error); + return { + ...DEFAULT_RESPONSE, + error: error instanceof Error ? error.message : "Network error", + } as ApiResponse; + } +} + +/** + * Fetch data without authentication + */ +async function fetchData( + endpoint: string, + payload?: any, + method: HttpMethod = "POST", + cache: boolean = false, + timeout: number = DEFAULT_TIMEOUT +): Promise> { + return coreFetch( + endpoint, + { method, cache, timeout }, + defaultHeaders, + payload + ); +} + +/** + * Fetch data with authentication token + */ +async function fetchDataWithToken( + endpoint: string, + payload?: any, + method: HttpMethod = "POST", + cache: boolean = false, + timeout: number = DEFAULT_TIMEOUT +): Promise> { + const accessToken = (await retrieveAccessToken()) || ""; + const headers = { + ...defaultHeaders, + "eys-acs-tkn": accessToken, + }; + + return coreFetch(endpoint, { method, cache, timeout }, headers, payload); +} + +/** + * Update data with authentication token and UUID + */ +async function updateDataWithToken( + endpoint: string, + uuid: string, + payload?: any, + method: HttpMethod = "POST", + cache: boolean = false, + timeout: number = DEFAULT_TIMEOUT +): Promise> { + const accessToken = (await retrieveAccessToken()) || ""; + const headers = { + ...defaultHeaders, + "eys-acs-tkn": accessToken, + }; + + return coreFetch( + `${endpoint}/${uuid}`, + { method, cache, timeout }, + headers, + payload + ); +} + +export { fetchData, fetchDataWithToken, updateDataWithToken }; diff --git a/web_services/management_frontend/src/apicalls/basics.ts b/web_services/management_frontend/src/apicalls/basics.ts new file mode 100644 index 0000000..b74f7a2 --- /dev/null +++ b/web_services/management_frontend/src/apicalls/basics.ts @@ -0,0 +1,67 @@ +const formatServiceUrl = (url: string) => { + if (!url) return ""; + return url.startsWith("http") ? url : `http://${url}`; +}; + +const baseUrlAuth = formatServiceUrl( + process.env.NEXT_PUBLIC_AUTH_SERVICE_URL || "auth_service:8001" +); +const baseUrlPeople = formatServiceUrl( + process.env.NEXT_PUBLIC_VALIDATION_SERVICE_URL || "identity_service:8002" +); +const baseUrlApplication = formatServiceUrl( + process.env.NEXT_PUBLIC_MANAGEMENT_SERVICE_URL || "management_service:8004" +); + +// Types for better type safety +type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; + +interface ApiResponse { + status: number; + data: T; + error?: string; +} + +interface FetchOptions { + method?: HttpMethod; + cache?: boolean; + timeout?: number; +} + +const tokenSecret = process.env.TOKENSECRET_90 || ""; + +const cookieObject: any = { + httpOnly: true, + path: "/", + sameSite: "none", + secure: true, + maxAge: 3600, + priority: "high", +}; + +// Constants +const DEFAULT_TIMEOUT = 10000; // 10 seconds +const defaultHeaders = { + accept: "application/json", + language: "tr", + domain: "management.com.tr", + tz: "GMT+3", + "Content-type": "application/json", +}; +const DEFAULT_RESPONSE: ApiResponse = { + error: "Hata tipi belirtilmedi", + status: 500, + data: {}, +}; + +export type { HttpMethod, ApiResponse, FetchOptions }; +export { + DEFAULT_TIMEOUT, + DEFAULT_RESPONSE, + defaultHeaders, + baseUrlAuth, + baseUrlPeople, + baseUrlApplication, + tokenSecret, + cookieObject, +}; diff --git a/web_services/management_frontend/src/apicalls/custom/login/login.tsx b/web_services/management_frontend/src/apicalls/custom/login/login.tsx new file mode 100644 index 0000000..b551237 --- /dev/null +++ b/web_services/management_frontend/src/apicalls/custom/login/login.tsx @@ -0,0 +1,182 @@ +"use server"; +import NextCrypto from "next-crypto"; +import { fetchData, fetchDataWithToken } from "@/apicalls/api-fetcher"; +import { baseUrlAuth, cookieObject, tokenSecret } from "@/apicalls/basics"; +import { cookies } from "next/headers"; + +const loginEndpoint = `${baseUrlAuth}/authentication/login`; +const loginSelectEndpoint = `${baseUrlAuth}/authentication/select`; +const logoutEndpoint = `${baseUrlAuth}/authentication/logout`; + +console.log("loginEndpoint", loginEndpoint); +console.log("loginSelectEndpoint", loginSelectEndpoint); + +interface LoginViaAccessKeys { + accessKey: string; + password: string; + rememberMe: boolean; +} + +interface LoginSelectEmployee { + company_uu_id: string; +} + +interface LoginSelectOccupant { + build_living_space_uu_id: any; +} + +async function logoutActiveSession() { + const cookieStore = await cookies(); + const response = await fetchDataWithToken(logoutEndpoint, {}, "GET", false); + cookieStore.delete("accessToken"); + cookieStore.delete("accessObject"); + cookieStore.delete("userProfile"); + cookieStore.delete("userSelection"); + return response; +} + +async function loginViaAccessKeys(payload: LoginViaAccessKeys) { + try { + const cookieStore = await cookies(); + const nextCrypto = new NextCrypto(tokenSecret); + + const response = await fetchData( + loginEndpoint, + { + access_key: payload.accessKey, + password: payload.password, + remember_me: payload.rememberMe, + }, + "POST", + false + ); + console.log("response", response); + if (response.status === 200 || response.status === 202) { + const loginRespone: any = response?.data; + const accessToken = await nextCrypto.encrypt(loginRespone.access_token); + const accessObject = await nextCrypto.encrypt( + JSON.stringify({ + userType: loginRespone.user_type, + selectionList: loginRespone.selection_list, + }) + ); + const userProfile = await nextCrypto.encrypt( + JSON.stringify(loginRespone.user) + ); + + cookieStore.set({ + name: "accessToken", + value: accessToken, + ...cookieObject, + }); + cookieStore.set({ + name: "accessObject", + value: accessObject, + ...cookieObject, + }); + cookieStore.set({ + name: "userProfile", + value: JSON.stringify(userProfile), + ...cookieObject, + }); + try { + return { + completed: true, + message: "Login successful", + status: 200, + data: loginRespone, + }; + } catch (error) { + console.error("JSON parse error:", error); + return { + completed: false, + message: "Login NOT successful", + status: 401, + data: "{}", + }; + } + } + + return { + completed: false, + // error: response.error || "Login failed", + // message: response.message || "Authentication failed", + status: response.status || 500, + }; + } catch (error) { + console.error("Login error:", error); + return { + completed: false, + // error: error instanceof Error ? error.message : "Login error", + // message: "An error occurred during login", + status: 500, + }; + } +} + +async function loginSelectEmployee(payload: LoginSelectEmployee) { + const cookieStore = await cookies(); + const nextCrypto = new NextCrypto(tokenSecret); + const companyUUID = payload.company_uu_id; + const selectResponse: any = await fetchDataWithToken( + loginSelectEndpoint, + { + company_uu_id: companyUUID, + }, + "POST", + false + ); + cookieStore.delete("userSelection"); + + if (selectResponse.status === 200 || selectResponse.status === 202) { + const usersSelection = await nextCrypto.encrypt( + JSON.stringify({ + selected: companyUUID, + user_type: "employee", + }) + ); + cookieStore.set({ + name: "userSelection", + value: usersSelection, + ...cookieObject, + }); + } + return selectResponse; +} + +async function loginSelectOccupant(payload: LoginSelectOccupant) { + const livingSpaceUUID = payload.build_living_space_uu_id; + const cookieStore = await cookies(); + const nextCrypto = new NextCrypto(tokenSecret); + const selectResponse: any = await fetchDataWithToken( + loginSelectEndpoint, + { + build_living_space_uu_id: livingSpaceUUID, + }, + "POST", + false + ); + cookieStore.delete("userSelection"); + + if (selectResponse.status === 200 || selectResponse.status === 202) { + const usersSelection = await nextCrypto.encrypt( + JSON.stringify({ + selected: livingSpaceUUID, + user_type: "occupant", + }) + ); + cookieStore.set({ + name: "userSelection", + value: usersSelection, + ...cookieObject, + }); + } + return selectResponse; +} + +export { + loginViaAccessKeys, + loginSelectEmployee, + loginSelectOccupant, + logoutActiveSession, +}; diff --git a/web_services/management_frontend/src/apicalls/mutual/cookies/token.tsx b/web_services/management_frontend/src/apicalls/mutual/cookies/token.tsx new file mode 100644 index 0000000..794ab5e --- /dev/null +++ b/web_services/management_frontend/src/apicalls/mutual/cookies/token.tsx @@ -0,0 +1,148 @@ +"use server"; +import { fetchDataWithToken } from "@/apicalls/api-fetcher"; +import { baseUrlAuth, tokenSecret } from "@/apicalls/basics"; +import { cookies } from "next/headers"; + +import NextCrypto from "next-crypto"; + +const checkToken = `${baseUrlAuth}/authentication/token/check`; +const pageValid = `${baseUrlAuth}/authentication/page/valid`; +const siteUrls = `${baseUrlAuth}/authentication/sites/list`; + +const nextCrypto = new NextCrypto(tokenSecret); + +async function checkAccessTokenIsValid() { + const response = await fetchDataWithToken(checkToken, {}, "GET", false); + return response?.status === 200 || response?.status === 202 ? true : false; +} + +async function retrievePageList() { + const response: any = await fetchDataWithToken(siteUrls, {}, "GET", false); + return response?.status === 200 || response?.status === 202 + ? response.data?.sites + : null; +} + +async function retrieveApplicationbyUrl(pageUrl: string) { + const response: any = await fetchDataWithToken( + pageValid, + { + page_url: pageUrl, + }, + "POST", + false + ); + return response?.status === 200 || response?.status === 202 + ? response.data?.application + : null; +} + +async function retrieveAccessToken() { + const cookieStore = await cookies(); + const encrpytAccessToken = cookieStore.get("accessToken")?.value || ""; + return encrpytAccessToken + ? await nextCrypto.decrypt(encrpytAccessToken) + : null; +} + +async function retrieveUserType() { + const cookieStore = await cookies(); + const encrpytaccessObject = cookieStore.get("accessObject")?.value || "{}"; + const decrpytUserType = JSON.parse( + (await nextCrypto.decrypt(encrpytaccessObject)) || "{}" + ); + return decrpytUserType ? decrpytUserType : null; +} + +async function retrieveAccessObjects() { + const cookieStore = await cookies(); + const encrpytAccessObject = cookieStore.get("accessObject")?.value || ""; + const decrpytAccessObject = await nextCrypto.decrypt(encrpytAccessObject); + return decrpytAccessObject ? JSON.parse(decrpytAccessObject) : null; +} + +async function retrieveUserSelection() { + const cookieStore = await cookies(); + const encrpytUserSelection = cookieStore.get("userSelection")?.value || ""; + + let objectUserSelection = {}; + let decrpytUserSelection: any = await nextCrypto.decrypt( + encrpytUserSelection + ); + decrpytUserSelection = decrpytUserSelection + ? JSON.parse(decrpytUserSelection) + : null; + console.log("decrpytUserSelection", decrpytUserSelection); + const userSelection = decrpytUserSelection?.selected; + const accessObjects = (await retrieveAccessObjects()) || {}; + console.log("accessObjects", accessObjects); + + if (decrpytUserSelection?.user_type === "employee") { + const companyList = accessObjects?.selectionList; + const selectedCompany = companyList.find( + (company: any) => company.uu_id === userSelection + ); + if (selectedCompany) { + objectUserSelection = { userType: "employee", selected: selectedCompany }; + } + } else if (decrpytUserSelection?.user_type === "occupant") { + const buildingsList = accessObjects?.selectionList; + + // Iterate through all buildings + if (buildingsList) { + // Loop through each building + for (const buildKey in buildingsList) { + const building = buildingsList[buildKey]; + + // Check if the building has occupants + if (building.occupants && building.occupants.length > 0) { + // Find the occupant with the matching build_living_space_uu_id + const occupant = building.occupants.find( + (occ: any) => occ.build_living_space_uu_id === userSelection + ); + + if (occupant) { + objectUserSelection = { + userType: "occupant", + selected: { + ...occupant, + buildName: building.build_name, + buildNo: building.build_no, + }, + }; + break; + } + } + } + } + } + return { + ...objectUserSelection, + }; +} + +// const avatarInfo = await retrieveAvatarInfo(); +// lang: avatarInfo?.data?.lang +// ? String(avatarInfo?.data?.lang).toLowerCase() +// : undefined, +// avatar: avatarInfo?.data?.avatar, +// fullName: avatarInfo?.data?.full_name, +// async function retrieveAvatarInfo() { +// const response = await fetchDataWithToken( +// `${baseUrlAuth}/authentication/avatar`, +// {}, +// "POST" +// ); +// return response; +// } + +export { + checkAccessTokenIsValid, + retrieveAccessToken, + retrieveUserType, + retrieveAccessObjects, + retrieveUserSelection, + retrieveApplicationbyUrl, + retrievePageList, + // retrieveavailablePages, +}; diff --git a/web_services/management_frontend/src/app/api/cookies/selection/route.ts b/web_services/management_frontend/src/app/api/cookies/selection/route.ts new file mode 100644 index 0000000..2d37fe2 --- /dev/null +++ b/web_services/management_frontend/src/app/api/cookies/selection/route.ts @@ -0,0 +1,20 @@ +import { retrieveUserSelection } from "@/apicalls/cookies/token"; +import { NextResponse } from "next/server"; + +export async function POST(): Promise { + try { + const userSelection = await retrieveUserSelection(); + console.log("userSelection", userSelection); + if (userSelection) { + return NextResponse.json({ + status: 200, + message: "User selection found", + data: userSelection, + }); + } + } catch (error) {} + return NextResponse.json({ + status: 500, + message: "User selection not found", + }); +} diff --git a/web_services/management_frontend/src/app/api/login/email/route.ts b/web_services/management_frontend/src/app/api/login/email/route.ts new file mode 100644 index 0000000..1edf11b --- /dev/null +++ b/web_services/management_frontend/src/app/api/login/email/route.ts @@ -0,0 +1,39 @@ +import { loginViaAccessKeys } from "@/apicalls/custom/login/login"; +import { NextResponse } from "next/server"; +import { loginSchemaEmail } from "@/webPages/auth/login/schemas"; + +export async function POST(req: Request): Promise { + try { + const headers = req.headers; + console.log("headers", Object.entries(headers)); + const body = await req.json(); + const dataValidated = { + accessKey: body.email, + password: body.password, + rememberMe: body.rememberMe, + }; + const validatedLoginBody = loginSchemaEmail.safeParse(body); + if (!validatedLoginBody.success) { + return NextResponse.json({ + status: 422, + message: validatedLoginBody.error.message, + }); + } + + const userLogin = await loginViaAccessKeys(dataValidated); + if (userLogin.status === 200 || userLogin.status === 202) { + return NextResponse.json({ + status: 200, + message: "Login successfully completed", + data: userLogin.data, + }); + } else { + return NextResponse.json({ + status: userLogin.status, + message: userLogin.message, + }); + } + } catch (error) { + return NextResponse.json({ status: 401, message: "Invalid credentials" }); + } +} diff --git a/web_services/management_frontend/src/app/api/selection/employee/route.ts b/web_services/management_frontend/src/app/api/selection/employee/route.ts new file mode 100644 index 0000000..3ab32d5 --- /dev/null +++ b/web_services/management_frontend/src/app/api/selection/employee/route.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; +import { loginSelectEmployee } from "@/apicalls/custom/login/login"; +import { NextResponse } from "next/server"; + +const loginSchemaEmployee = z.object({ + company_uu_id: z.string(), +}); + +export async function POST(req: Request): Promise { + try { + const headers = req.headers; + console.log("headers", Object.entries(headers)); + const body = await req.json(); + const dataValidated = { + company_uu_id: body.company_uu_id, + }; + const validatedLoginBody = loginSchemaEmployee.safeParse(body); + if (!validatedLoginBody.success) { + return NextResponse.json({ + status: 422, + message: validatedLoginBody.error.message, + }); + } + + const userLogin = await loginSelectEmployee(dataValidated); + if (userLogin.status === 200 || userLogin.status === 202) { + return NextResponse.json({ + status: 200, + message: "Selection successfully completed", + data: userLogin.data, + }); + } else { + return NextResponse.json({ + status: userLogin.status, + message: userLogin.message, + }); + } + } catch (error) { + return NextResponse.json({ status: 401, message: "Invalid credentials" }); + } +} diff --git a/web_services/management_frontend/src/app/api/selection/occupant/route.ts b/web_services/management_frontend/src/app/api/selection/occupant/route.ts new file mode 100644 index 0000000..405c361 --- /dev/null +++ b/web_services/management_frontend/src/app/api/selection/occupant/route.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; +import { loginSelectOccupant } from "@/apicalls/custom/login/login"; +import { NextResponse } from "next/server"; + +const loginSchemaOccupant = z.object({ + build_living_space_uu_id: z.string(), +}); + +export async function POST(req: Request): Promise { + try { + const headers = req.headers; + console.log("headers", Object.entries(headers)); + const body = await req.json(); + const dataValidated = { + build_living_space_uu_id: body.build_living_space_uu_id, + }; + const validatedLoginBody = loginSchemaOccupant.safeParse(body); + if (!validatedLoginBody.success) { + return NextResponse.json({ + status: 422, + message: validatedLoginBody.error.message, + }); + } + + const userLogin = await loginSelectOccupant(dataValidated); + if (userLogin.status === 200 || userLogin.status === 202) { + return NextResponse.json({ + status: 200, + message: "Selection successfully completed", + data: userLogin.data, + }); + } else { + return NextResponse.json({ + status: userLogin.status, + message: userLogin.message, + }); + } + } catch (error) { + return NextResponse.json({ status: 401, message: "Invalid credentials" }); + } +} diff --git a/web_services/management_frontend/src/app/auth/en/[...page]/page.tsx b/web_services/management_frontend/src/app/auth/en/[...page]/page.tsx new file mode 100644 index 0000000..95b69c2 --- /dev/null +++ b/web_services/management_frontend/src/app/auth/en/[...page]/page.tsx @@ -0,0 +1,15 @@ +'use server'; +import { AuthLayout } from "@/layouts/auth/layout"; +import { AuthServerProps } from "@/validations/mutual/pages/props"; +import getPage from "@/webPages/getPage"; + +const AuthPageEn = async ({ params, searchParams }: AuthServerProps) => { + const lang = "en"; + const awaitedParams = await params; + const awaitedSearchParams = await searchParams; + const pageUrlFromParams = `/${awaitedParams.page?.join("/")}` || "/login"; + const FoundPage = getPage(pageUrlFromParams, { language: lang, query: awaitedSearchParams }); + return +} + +export default AuthPageEn; diff --git a/web_services/management_frontend/src/app/auth/tr/[...page]/page.tsx b/web_services/management_frontend/src/app/auth/tr/[...page]/page.tsx new file mode 100644 index 0000000..0932203 --- /dev/null +++ b/web_services/management_frontend/src/app/auth/tr/[...page]/page.tsx @@ -0,0 +1,15 @@ +'use server'; +import { AuthServerProps } from "@/validations/mutual/pages/props"; +import { AuthLayout } from "@/layouts/auth/layout"; +import getPage from "@/webPages/getPage"; + +const AuthPageTr = async ({ params, searchParams }: AuthServerProps) => { + const lang = "tr"; + const awaitedParams = await params; + const awaitedSearchParams = await searchParams; + const pageUrlFromParams = `/${awaitedParams.page?.join("/")}` || "/login"; + const FoundPage = getPage(pageUrlFromParams, { language: lang, query: awaitedSearchParams }); + return +} + +export default AuthPageTr; diff --git a/web_services/management_frontend/src/app/en/[...page]/page.tsx b/web_services/management_frontend/src/app/panel/en/[...page]/page.tsx similarity index 100% rename from web_services/management_frontend/src/app/en/[...page]/page.tsx rename to web_services/management_frontend/src/app/panel/en/[...page]/page.tsx diff --git a/web_services/management_frontend/src/app/en/page.tsx b/web_services/management_frontend/src/app/panel/en/page.tsx similarity index 100% rename from web_services/management_frontend/src/app/en/page.tsx rename to web_services/management_frontend/src/app/panel/en/page.tsx diff --git a/web_services/management_frontend/src/app/tr/[...page]/page.tsx b/web_services/management_frontend/src/app/panel/tr/[...page]/page.tsx similarity index 100% rename from web_services/management_frontend/src/app/tr/[...page]/page.tsx rename to web_services/management_frontend/src/app/panel/tr/[...page]/page.tsx diff --git a/web_services/management_frontend/src/components/custom/header/component.tsx b/web_services/management_frontend/src/components/custom/header/component.tsx index fccc23c..6c484a0 100644 --- a/web_services/management_frontend/src/components/custom/header/component.tsx +++ b/web_services/management_frontend/src/components/custom/header/component.tsx @@ -4,7 +4,7 @@ import { HeaderProps } from "@/validations/mutual/dashboard/props"; import LanguageSelectionComponent from "@/components/mutual/languageSelection/component"; import { langGetKey } from "@/lib/langGet"; -const HeaderComponent: FC = ({ translations, lang, activePageUrl }) => { +const HeaderComponent: FC = ({ translations, lang, activePageUrl, prefix }) => { return (
@@ -12,7 +12,7 @@ const HeaderComponent: FC = ({ translations, lang, activePageUrl })

{langGetKey(translations, 'selectedPage')} :

{langGetKey(translations, 'page')}

- + ); }; diff --git a/web_services/management_frontend/src/components/custom/menu/single/component.tsx b/web_services/management_frontend/src/components/custom/menu/single/component.tsx index dedc23b..00f56e2 100644 --- a/web_services/management_frontend/src/components/custom/menu/single/component.tsx +++ b/web_services/management_frontend/src/components/custom/menu/single/component.tsx @@ -6,18 +6,20 @@ import { MenuSingleProps, SingleLayerItemProps } from "@/validations/mutual/dash import { langGetKey } from "@/lib/langGet"; const SingleLayerItem: FC = ({ isActive, innerText, url }) => { + console.log({ isActive, innerText, url }) let className = "py-3 px-4 text-sm rounded-xl cursor-pointer transition-colors duration-200 flex justify-between items-center w-full"; - if (isActive) { className += " bg-emerald-700 text-white font-medium" } + if (isActive) { className += " bg-black text-white font-medium" } else { className += " bg-emerald-800 text-white hover:bg-emerald-700" } - return {innerText} + if (isActive) { return
{innerText}
} + else { return {innerText} } }; -const MenuComponent: FC = ({ lang, activePageUrl, translations, menuItems }) => { +const MenuComponent: FC = ({ lang, activePageUrl, translations, menuItems, prefix }) => { const renderMenuItems = () => { - return menuItems.map((key) => { - const url = `/${lang}/${key}`; - const isActive = activePageUrl === key; + return Object.keys(menuItems).map((key) => { + const url = `${prefix}/${lang}${menuItems[key]}`; + const isActive = `${activePageUrl}` === `${key}`; return
}); }; diff --git a/web_services/management_frontend/src/components/mutual/languageSelection/component.tsx b/web_services/management_frontend/src/components/mutual/languageSelection/component.tsx index e5af966..1a83e57 100644 --- a/web_services/management_frontend/src/components/mutual/languageSelection/component.tsx +++ b/web_services/management_frontend/src/components/mutual/languageSelection/component.tsx @@ -7,9 +7,9 @@ import { LanguageTypes } from "@/validations/mutual/language/validations"; import LanguageSelectionItem from "./languageItem"; -const LanguageSelectionComponent: React.FC<{ lang: LanguageTypes, activePage: string }> = ({ lang, activePage }) => { +const LanguageSelectionComponent: React.FC<{ lang: LanguageTypes, activePage: string, prefix: string }> = ({ lang, activePage, prefix }) => { const translations = langGet(lang, languageSelectionTranslation); - const getPageWithLocale = (locale: LanguageTypes): string => { return `/${locale}/${activePage}` } + const getPageWithLocale = (locale: LanguageTypes): string => { return `${prefix}/${locale}/${activePage}` } const englishButtonProps = { activeLang: lang, @@ -30,8 +30,7 @@ const LanguageSelectionComponent: React.FC<{ lang: LanguageTypes, activePage: st - - + ); diff --git a/web_services/management_frontend/src/languages/custom/index.ts b/web_services/management_frontend/src/languages/custom/index.ts index 21fa2bb..2595e14 100644 --- a/web_services/management_frontend/src/languages/custom/index.ts +++ b/web_services/management_frontend/src/languages/custom/index.ts @@ -4,6 +4,7 @@ import { managementAccountTenantMain } from "./management/account/tenantSomethin import { managementAccountTenantMainSecond } from "./management/account/tenantSomethingSecond/index"; const dynamicPagesIndex: Record> = { + dashboard: managementAccountTenantMain, application: managementAccountTenantMain, services: managementAccountTenantMainSecond, }; diff --git a/web_services/management_frontend/src/layouts/auth/layout.tsx b/web_services/management_frontend/src/layouts/auth/layout.tsx new file mode 100644 index 0000000..04dc662 --- /dev/null +++ b/web_services/management_frontend/src/layouts/auth/layout.tsx @@ -0,0 +1,23 @@ +'use server'; +import { FC, Suspense } from "react"; +import { AuthLayoutProps } from "@/validations/mutual/auth/props"; +import LanguageSelectionComponent from "@/components/mutual/languageSelection/component"; + +const AuthLayout: FC = async ({ lang, page, activePageUrl }) => { + return ( +
+
+
+
WAG Frontend
+
Welcome to the WAG Frontend Application
+
+
+
+ + Loading...
}>{page} +
+ + ); +} + +export { AuthLayout }; \ No newline at end of file diff --git a/web_services/management_frontend/src/layouts/dashboard/layout.tsx b/web_services/management_frontend/src/layouts/dashboard/layout.tsx index 54f68c0..e86f03a 100644 --- a/web_services/management_frontend/src/layouts/dashboard/layout.tsx +++ b/web_services/management_frontend/src/layouts/dashboard/layout.tsx @@ -10,12 +10,31 @@ import ContentComponent from "@/components/custom/content/component"; import FooterComponent from "@/components/custom/footer/component"; import { menuTranslation } from "@/languages/mutual/menu"; +const menuTranslations = { + en: { + "dashboard": "Dashboard", + "application": "Application", + "services": "Services", + }, + tr: { + "dashboard": "Panel", + "application": "Uygulama", + "services": "Servisler", + }, +} + +const menuUrlIndex = { + dashboard: "/dashboard", + application: "/application", + services: "/services", +} + const DashboardLayout: FC = async ({ params, searchParams, lang }) => { - const activePageUrl = `/${lang}/${params?.page?.join("/")}`; + const activePageUrl = `${params?.page?.join("/")}`; const mode = (searchParams?.mode as ModeTypes) || 'shortList'; const translations = langGet(lang, langDynamicPagesGet(activePageUrl, dynamicPagesIndex)); - const headerProps = { translations: translations.header, lang, activePageUrl } - const menuProps = { lang, activePageUrl, translations: menuTranslation[lang], menuItems: Object.keys(translations.menu) } + const headerProps = { translations: translations.header, lang, activePageUrl, prefix: "/panel" } + const menuProps = { lang, activePageUrl, translations: menuTranslations[lang], menuItems: menuUrlIndex, prefix: "/panel" } const contentProps = { translations: translations.content, lang, activePageUrl, mode, isMulti: false } return ( diff --git a/web_services/management_frontend/src/pages/resolver/resolver.tsx b/web_services/management_frontend/src/pages/resolver/resolver.tsx index e066f99..de5b1f3 100644 --- a/web_services/management_frontend/src/pages/resolver/resolver.tsx +++ b/web_services/management_frontend/src/pages/resolver/resolver.tsx @@ -5,12 +5,12 @@ import { ContentProps } from "@/validations/mutual/dashboard/props"; import pageIndexMulti from "@/pages/multi/index"; import pageIndexSingle from "@/pages/single/index"; +import ContentToRenderNoPage from "@/pages/mutual/noContent/page"; function resolveWhichPageToRenderSingle({ activePageUrl, }: ResolverProps): React.FC { - const ApplicationToRender = pageIndexSingle[activePageUrl] - return ApplicationToRender + return activePageUrl in pageIndexSingle ? pageIndexSingle[activePageUrl] : ContentToRenderNoPage } async function resolveWhichPageToRenderMulti({ @@ -23,7 +23,7 @@ async function resolveWhichPageToRenderMulti({ } catch (error) { console.error(error) } - return null + return ContentToRenderNoPage } export { resolveWhichPageToRenderSingle, resolveWhichPageToRenderMulti }; diff --git a/web_services/management_frontend/src/pages/single/index.ts b/web_services/management_frontend/src/pages/single/index.ts index 966f14b..d113732 100644 --- a/web_services/management_frontend/src/pages/single/index.ts +++ b/web_services/management_frontend/src/pages/single/index.ts @@ -4,9 +4,9 @@ import superUserTenantSomethingSecond from "./management/account/tenantSomething import superUserBuildingPartsTenantSomething from "./building/parts/tenantSomething/page"; const pageIndexSingle: Record> = { - "management/account/tenant/something": superUserTenantSomething, - "management/account/tenant/somethingSecond": superUserTenantSomethingSecond, - "building/parts/tenant/something": superUserBuildingPartsTenantSomething, + dashboard: superUserTenantSomething, + application: superUserTenantSomethingSecond, + service: superUserBuildingPartsTenantSomething, }; export default pageIndexSingle; diff --git a/web_services/management_frontend/src/validations/mutual/auth/props.ts b/web_services/management_frontend/src/validations/mutual/auth/props.ts new file mode 100644 index 0000000..2a8169a --- /dev/null +++ b/web_services/management_frontend/src/validations/mutual/auth/props.ts @@ -0,0 +1,13 @@ +import { LanguageTypes } from "../language/validations"; + +// +interface AuthLayoutProps { + page: React.ReactNode; + lang: LanguageTypes; + activePageUrl: string; +} +interface AuthPageProps { + query?: { [key: string]: string | string[] | undefined }; + language: LanguageTypes; +} +export type { AuthLayoutProps, AuthPageProps }; diff --git a/web_services/management_frontend/src/validations/mutual/dashboard/props.ts b/web_services/management_frontend/src/validations/mutual/dashboard/props.ts index 0a6dda8..58d7f69 100644 --- a/web_services/management_frontend/src/validations/mutual/dashboard/props.ts +++ b/web_services/management_frontend/src/validations/mutual/dashboard/props.ts @@ -35,7 +35,8 @@ interface MenuSingleProps { lang: LanguageTypes; activePageUrl: string; translations: Record; - menuItems: string[]; + menuItems: Record; + prefix: string; } interface FooterProps { @@ -46,6 +47,7 @@ interface HeaderProps { translations: Record; lang: LanguageTypes; activePageUrl: string; + prefix: string; } interface SingleLayerItemProps { diff --git a/web_services/management_frontend/src/validations/mutual/pages/props.ts b/web_services/management_frontend/src/validations/mutual/pages/props.ts new file mode 100644 index 0000000..7148508 --- /dev/null +++ b/web_services/management_frontend/src/validations/mutual/pages/props.ts @@ -0,0 +1,7 @@ +type ParamsType = { page: string[] | undefined }; +interface AuthServerProps { + params: Promise<{ page: string[] | undefined }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; +} + +export type { ParamsType, AuthServerProps }; diff --git a/web_services/management_frontend/src/webPages/auth/login/hook.ts b/web_services/management_frontend/src/webPages/auth/login/hook.ts new file mode 100644 index 0000000..b87ae9a --- /dev/null +++ b/web_services/management_frontend/src/webPages/auth/login/hook.ts @@ -0,0 +1,44 @@ +import { LanguageTypes } from "@/validations/mutual/language/validations"; + +export function loginHook( + startTransition: any, + data: any, + setError: any, + setJsonText: any, + Router: any, + lang: LanguageTypes +) { + try { + const sendData = { ...data }; + startTransition(() => { + fetch("/api/login/email", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(sendData), + }) + .then((response) => { + if (response.status === 200) { + response.json().then((data) => { + console.log("data", data); // setJsonText(JSON.stringify(data)); + setTimeout(() => { + const userType = + data?.data?.user_type.toLowerCase() === "employee" + ? "employee" + : "occupant"; + const rediretUrl = `/auth/${lang}/select?type=${userType}`; + console.log("rediretUrl", rediretUrl); + Router.push(rediretUrl); + }, 100); + }); + } else { + response.json().then((data) => { + setError(data?.message); + }); + } + }) + .catch(() => {}); + }); + } catch (error) { + setError("An error occurred during login"); + } +} diff --git a/web_services/management_frontend/src/webPages/auth/login/language.ts b/web_services/management_frontend/src/webPages/auth/login/language.ts new file mode 100644 index 0000000..0c2dddb --- /dev/null +++ b/web_services/management_frontend/src/webPages/auth/login/language.ts @@ -0,0 +1,22 @@ +export const loginTranslation = { + tr: { + email: "E-Posta", + password: "Şifre", + rememberMe: "Beni Hatırla", + login: "Giriş Yap", + successMessage: "Giriş Yapıldı", + errorMessage: "Giriş Yapılmadı", + pendingMessage: "Giriş Yapılıyor...", + responseData: "Giriş Verileri", + }, + en: { + email: "Email", + password: "Password", + rememberMe: "Remember Me", + login: "Login", + successMessage: "Login completed successfully", + errorMessage: "Login failed", + pendingMessage: "Logging in...", + responseData: "Response Data", + }, +}; diff --git a/web_services/management_frontend/src/webPages/auth/login/page.tsx b/web_services/management_frontend/src/webPages/auth/login/page.tsx new file mode 100644 index 0000000..f440915 --- /dev/null +++ b/web_services/management_frontend/src/webPages/auth/login/page.tsx @@ -0,0 +1,62 @@ +"use client"; +import { useState, useTransition } from "react"; +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { LoginFormData } from "./types"; +import { loginSchemaEmail } from "./schemas"; +import { loginTranslation } from "./language"; +import { loginHook } from "./hook"; +import { AuthPageProps } from "@/validations/mutual/auth/props"; + +function Login({ language }: AuthPageProps) { + const Router = useRouter(); + const translation = loginTranslation[language]; + + const [isPending, startTransition] = useTransition(); + const [error, setError] = useState(null); + const [jsonText, setJsonText] = useState(null); + + const { register, formState: { errors }, handleSubmit, } = useForm({ resolver: zodResolver(loginSchemaEmail) }); + const onSubmit = async (data: LoginFormData) => { loginHook(startTransition, data, setError, setJsonText, Router, language) }; + + return ( + <> +
+
+

{translation.login}

+
+
+ + + {errors.email && (

{errors.email.message}

)} +
+
+ + + {errors.password && (

{errors.password.message}

)} +
+ {error && (

{error}

)} + +
+
+ {jsonText && ( +
+

{translation.responseData}

+
+ {Object.entries(JSON.parse(jsonText)).map(([key, value]) => ( +
+ {key}: + {typeof value === "object" ? JSON.stringify(value) : value?.toString() || "N/A"} +
+ ))} +
+
+ )} +
+ + ); +} + +export default Login; diff --git a/web_services/management_frontend/src/webPages/auth/login/schemas.ts b/web_services/management_frontend/src/webPages/auth/login/schemas.ts new file mode 100644 index 0000000..9b688a4 --- /dev/null +++ b/web_services/management_frontend/src/webPages/auth/login/schemas.ts @@ -0,0 +1,15 @@ +import { z } from "zod"; + +const loginSchemaEmail = z.object({ + email: z.string().email("Invalid email address"), + password: z.string().min(5, "Password must be at least 5 characters"), + rememberMe: z.boolean().optional().default(false), +}); + +const loginSchemaPhone = z.object({ + phone: z.string().regex(/^[0-9]{10}$/, "Invalid phone number"), + password: z.string().min(5, "Password must be at least 5 characters"), + rememberMe: z.boolean().optional().default(false), +}); + +export { loginSchemaEmail, loginSchemaPhone }; diff --git a/web_services/management_frontend/src/webPages/auth/login/serverPage.tsx b/web_services/management_frontend/src/webPages/auth/login/serverPage.tsx new file mode 100644 index 0000000..32d3425 --- /dev/null +++ b/web_services/management_frontend/src/webPages/auth/login/serverPage.tsx @@ -0,0 +1,8 @@ +'use server'; +import Login from "./page"; +import { FC } from "react"; +import { AuthPageProps } from "@/validations/mutual/auth/props"; + +const LoginPage: FC = async ({ query, language }) => { return }; + +export default LoginPage; diff --git a/web_services/management_frontend/src/webPages/auth/login/types.ts b/web_services/management_frontend/src/webPages/auth/login/types.ts new file mode 100644 index 0000000..0ed3a33 --- /dev/null +++ b/web_services/management_frontend/src/webPages/auth/login/types.ts @@ -0,0 +1,13 @@ +type LoginFormData = { + email: string; + password: string; + rememberMe?: boolean; +}; + +type LoginFormDataPhone = { + phone: string; + password: string; + rememberMe?: boolean; +}; + +export type { LoginFormData, LoginFormDataPhone }; diff --git a/web_services/management_frontend/src/webPages/auth/select/LoginEmployee.tsx b/web_services/management_frontend/src/webPages/auth/select/LoginEmployee.tsx new file mode 100644 index 0000000..6a24c64 --- /dev/null +++ b/web_services/management_frontend/src/webPages/auth/select/LoginEmployee.tsx @@ -0,0 +1,99 @@ +"use client"; +import React, { useTransition, useState } from "react"; +import { useRouter } from "next/navigation"; +import { Company } from "./types"; +import { LoginEmployeeProps } from "./types"; +import { selectEmployeeHook } from "./hook"; + +function LoginEmployee({ + selectionList, + translation, + lang +}: LoginEmployeeProps) { + const isArrayLengthOne = Array.isArray(selectionList) && selectionList.length === 1; + const isArrayLengthZero = Array.isArray(selectionList) && selectionList.length === 0; + const isArrayMoreThanOne = Array.isArray(selectionList) && selectionList.length > 1; + const Router = useRouter(); + + const [isPending, startTransition] = useTransition(); + const [error, setError] = useState(null); + const [jsonText, setJsonText] = useState(null); + + const onSubmitEmployee = async (uu_id: string) => { + selectEmployeeHook(startTransition, { company_uu_id: uu_id }, setError, setJsonText, Router, lang) + }; + + // Render a company card with consistent styling + const CompanyCard = ({ company, showButton = false }: { company: Company, showButton?: boolean }) => ( +
+
onSubmitEmployee(company.uu_id)}> + {/* Company name and type */} +
+

{company.public_name}

+ {company.company_type && ( + + {company.company_type} + + )} +
+ + {/* Duty information */} + {company.duty && ( +
+ {translation.duty}: {company.duty} +
+ )} + + {/* ID information */} +
+ {translation.id}: {company.uu_id} +
+ +
+
+ ); + + return ( +
+

{translation.companySelection}

+

{translation.loggedInAs}

+ + {/* No companies available */} + {isArrayLengthZero && ( +
+

{translation.noSelections}

+
+ )} + + {/* Single company */} + {isArrayLengthOne && } + + {/* Multiple companies */} + {isArrayMoreThanOne && ( +
+ {selectionList.map((company, index) => ( +
onSubmitEmployee(company.uu_id)} + className="cursor-pointer hover:translate-x-1 transition-transform" + > + +
+ ))} +
+ )} + + {/* Show error if any */} + {error &&

{error}

} + + {/* Loading indicator */} + {isPending && ( +
+
+
+ )} +
+ ); +} + +export default LoginEmployee; diff --git a/web_services/management_frontend/src/webPages/auth/select/LoginOccupant.tsx b/web_services/management_frontend/src/webPages/auth/select/LoginOccupant.tsx new file mode 100644 index 0000000..5f5edd5 --- /dev/null +++ b/web_services/management_frontend/src/webPages/auth/select/LoginOccupant.tsx @@ -0,0 +1,109 @@ +"use client"; +import React, { useState, useTransition, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { LoginOccupantProps } from "./types"; +import { selectOccupantHook } from "./hook"; + +function LoginOccupant({ + selectionList, + translation, + lang +}: LoginOccupantProps) { + + const Router = useRouter(); + const [isPending, startTransition] = useTransition(); + const [error, setError] = useState(null); + const [jsonText, setJsonText] = useState(null); + + const onSubmitOccupant = async (data: any) => { + selectOccupantHook(startTransition, data, setError, setJsonText, Router, lang) + }; + const isArrayLengthZero = Array.isArray(selectionList) && selectionList.length === 0; + + // Render an occupant card with consistent styling + const OccupantCard = ({ occupant, buildKey, idx }: { occupant: any, buildKey: string, idx: number }) => ( +
onSubmitOccupant({ build_living_space_uu_id: occupant.build_living_space_uu_id })} + > +
+ {/* Occupant description and code */} +
+

{occupant.description}

+ + {occupant.code} + +
+ + {/* Part name */} +
+ {occupant.part_name} +
+ + {/* Level information */} +
+ {translation.level}: {occupant.part_level} +
+
+
+ ); + + // Render a building section with its occupants + const BuildingSection = ({ building, buildKey }: { building: any, buildKey: string }) => ( +
+
+

+ + + + {building.build_name} + No: {building.build_no} +

+
+ +
+ {building.occupants.map((occupant: any, idx: number) => ( + + ))} +
+
+ ); + + return ( +
+

{translation.occupantSelection}

+

{translation.loggedInAs}

+ + {/* No occupants available */} + {!isArrayLengthZero ? ( +
+

{translation.noSelections}

+
+ ) : ( + /* Building sections with occupants */ +
+ {Object.keys(selectionList).map((buildKey: string) => ( + + ))} +
+ )} + + {/* Show error if any */} + {error &&

{error}

} + + {/* Loading indicator */} + {isPending && ( +
+
+
+ )} +
+ ); +} + +export default LoginOccupant; diff --git a/web_services/management_frontend/src/webPages/auth/select/hook.ts b/web_services/management_frontend/src/webPages/auth/select/hook.ts new file mode 100644 index 0000000..17fde74 --- /dev/null +++ b/web_services/management_frontend/src/webPages/auth/select/hook.ts @@ -0,0 +1,72 @@ +import { apiPostFetcher } from "@/lib/fetcher"; +import { LanguageTypes } from "@/validations/mutual/language/validations"; + +function selectEmployeeHook( + startTransition: any, + data: any, + setError: any, + setJsonText: any, + Router: any, + lang: LanguageTypes +) { + try { + const sendData = { ...data }; + const urlToDirect = `/panel/${lang}/dashboard`; + startTransition(() => { + apiPostFetcher({ + url: "/api/selection/employee", + isNoCache: true, + body: sendData, + }) + .then((response) => { + const data = response.data; + if (response.success) { + setTimeout(() => { + Router.push(urlToDirect); + }, 100); + } else { + setError(data?.message); + } + }) + .catch(() => {}); + }); + } catch (error) { + setError("An error occurred during login"); + } +} + +function selectOccupantHook( + startTransition: any, + data: any, + setError: any, + setJsonText: any, + Router: any, + lang: LanguageTypes +) { + try { + const sendData = { ...data }; + const urlToDirect = `/auth/${lang}/panel/dashboard`; + startTransition(() => { + apiPostFetcher({ + url: "/api/selection/occupant", + isNoCache: true, + body: sendData, + }) + .then((response) => { + const data = response.data; + if (response.success) { + setTimeout(() => { + Router.push(urlToDirect); + }, 100); + } else { + setError(data?.message); + } + }) + .catch(() => {}); + }); + } catch (error) { + setError("An error occurred during login"); + } +} + +export { selectEmployeeHook, selectOccupantHook }; diff --git a/web_services/management_frontend/src/webPages/auth/select/language.ts b/web_services/management_frontend/src/webPages/auth/select/language.ts new file mode 100644 index 0000000..1ecf7d0 --- /dev/null +++ b/web_services/management_frontend/src/webPages/auth/select/language.ts @@ -0,0 +1,44 @@ +export const selectEmployeeTranslation = { + tr: { + companySelection: "Şirket Seçimi", + loggedInAs: "Çalışan olarak giriş yaptınız", + duty: "Görev", + id: "Kimlik", + noSelections: "Seçenek bulunamadı", + continue: "Devam et", + select: "Konut Seçimi", + + }, + en: { + companySelection: "Select your company", + loggedInAs: "You are logged in as an employee", + duty: "Duty", + id: "ID", + noSelections: "No selections available", + continue: "Continue", + select: "Select Occupant", + + }, +}; + +export const selectOccupantTranslation = { + tr: { + occupantSelection: "Daire Seçimi", + loggedInAs: "Kiracı olarak giriş yaptınız", + buildingInfo: "Bina Bilgisi", + level: "Kat", + noSelections: "Seçenek bulunamadı", + continue: "Devam et", + select: "Şirket Seçimi", + + }, + en: { + occupantSelection: "Select your occupant type", + loggedInAs: "You are logged in as an occupant", + buildingInfo: "Building Info", + level: "Level", + continue: "Continue", + noSelections: "No selections available", + select: "Select Company", + }, +}; diff --git a/web_services/management_frontend/src/webPages/auth/select/page.tsx b/web_services/management_frontend/src/webPages/auth/select/page.tsx new file mode 100644 index 0000000..d277116 --- /dev/null +++ b/web_services/management_frontend/src/webPages/auth/select/page.tsx @@ -0,0 +1,43 @@ +"use client"; +import { useState, useEffect } from "react"; +import LoginOccupant from "./LoginOccupant"; +import LoginEmployee from "./LoginEmployee"; + +import { Company, SelectListProps, BuildingMap } from "./types"; +import { selectEmployeeTranslation, selectOccupantTranslation } from "./language"; + +const Select: React.FC = ({ selectionList, isEmployee, isOccupant, language, query }) => { + const isEmployeee = query?.isEmployee == "true"; + const isOccupante = query?.isOccupant == "true"; + const userType = isEmployeee || isOccupante ? isEmployeee ? "employee" : "occupant" : "employee"; + + const isEmployeeTrue = userType == "employee" && Array.isArray(selectionList) + const isOccupantTrue = userType == "occupant" && !Array.isArray(selectionList) + const initTranslation = userType == "employee" ? selectEmployeeTranslation[language] : selectOccupantTranslation[language] + + const [translation, setTranslation] = useState(initTranslation); + const [listEmployeeSelection, setListEmployeeSelection] = useState(selectionList as Company[]); + const [listOccupantSelection, setListOccupantSelection] = useState(selectionList as BuildingMap); + + useEffect(() => { + if (isEmployee) { setListEmployeeSelection(selectionList as Company[]) } + else if (isOccupant) { setListOccupantSelection(selectionList as BuildingMap) } + }, []); + + useEffect(() => { + setTranslation(isEmployee ? selectEmployeeTranslation[language] : selectOccupantTranslation[language]); + }, [language]); + + return ( + <> +
+
+ {isEmployeeTrue && } + {isOccupantTrue && } +
+
+ + ); +} + +export default Select; diff --git a/web_services/management_frontend/src/webPages/auth/select/serverPage.tsx b/web_services/management_frontend/src/webPages/auth/select/serverPage.tsx new file mode 100644 index 0000000..e5d4507 --- /dev/null +++ b/web_services/management_frontend/src/webPages/auth/select/serverPage.tsx @@ -0,0 +1,20 @@ +"use server"; +import React, { FC } from "react"; +import Select from "./page"; + +import { redirect } from "next/navigation"; +import { checkAccessTokenIsValid, retrieveUserType } from "@/apicalls/mutual/cookies/token"; +import { AuthPageProps } from "@/validations/mutual/auth/props"; + +const SelectPage: FC = async ({ query, language }) => { + const token_is_valid = await checkAccessTokenIsValid(); + const selection = await retrieveUserType(); + const isEmployee = selection?.userType == "employee"; + const isOccupant = selection?.userType == "occupant"; + const selectionList = selection?.selectionList; + + if (!selectionList || !token_is_valid) { redirect("/auth/en/login") } + return