client frontend added

This commit is contained in:
Berkay 2025-05-19 23:12:23 +03:00
parent 5f7cb35ccc
commit fdf9d2edb8
507 changed files with 40655 additions and 510 deletions

View File

@ -68,8 +68,14 @@ class LoginHandler:
return str(email).split("@")[1] == api_config.ACCESS_EMAIL_EXT
@classmethod
# headers: CommonHeaders
def do_employee_login(cls, headers: CommonHeaders, data: Any, db_session):
"""Handle employee login."""
language = headers.request.headers.get("language", "tr")
domain = headers.request.headers.get("domain", None)
timezone = headers.request.headers.get("tz", None) or "GMT+3"
user_handler, other_domains_list, main_domain = UserHandlers(), [], ""
found_user = user_handler.check_user_exists(access_key=data.access_key, db_session=db_session)
with mongo_handler.collection(f"{str(found_user.related_company)}*Domain") as collection:
@ -78,7 +84,7 @@ class LoginHandler:
raise ValueError("EYS_00087")
other_domains_list = result.get("other_domains_list", [])
main_domain = result.get("main_domain", None)
if headers.domain not in other_domains_list or not main_domain:
if domain not in other_domains_list or not main_domain:
raise ValueError("EYS_00088")
if not user_handler.check_password_valid(domain=main_domain, id_=str(found_user.uu_id), password=data.password, password_hashed=found_user.hash_password):
@ -115,24 +121,24 @@ class LoginHandler:
duty_uu_id_list.append(str(list_employee["Duty.uu_id"]))
duty_id_list.append(int(list_employee["Duty.id"]))
companies_list.append({
"uu_id": str(list_employee["Companies.uu_id"]), "public_name": list_employee["Companies.public_name"],
"company_type": list_employee["Companies.company_type"], "company_address": list_employee["Addresses.letter_address"],
"duty": list_employee["Duty.duty_name"]
"uu_id": str(list_employee["Employees.uu_id"]),
"public_name": list_employee["Companies.public_name"],
"company_type": list_employee["Companies.company_type"],
"company_address": list_employee["Addresses.letter_address"],
"duty": list_employee["Duty.duty_name"],
"company_uuid": str(list_employee["Companies.uu_id"])
})
model_value = EmployeeTokenObject(
user_type=UserType.employee.value,
user_uu_id=str(found_user.uu_id),
user_id=found_user.id,
person_id=found_user.person_id,
person_uu_id=str(list_employees[0]["People.uu_id"]),
request=dict(headers.request.headers),
domain_list=other_domains_list,
companies_uu_id_list=companies_uu_id_list,
companies_id_list=companies_id_list,
duty_uu_id_list=duty_uu_id_list,
duty_id_list=duty_id_list,
user_type=UserType.employee.value, user_uu_id=str(found_user.uu_id), user_id=found_user.id, person_id=found_user.person_id,
person_uu_id=str(list_employees[0]["People.uu_id"]), request=dict(headers.request.headers), domain_list=other_domains_list,
companies_uu_id_list=companies_uu_id_list, companies_id_list=companies_id_list, duty_uu_id_list=duty_uu_id_list, duty_id_list=duty_id_list,
).model_dump()
set_to_redis_dict = dict(user=found_user, token=model_value, header_info=dict(language=headers.language, domain=headers.domain, timezone=headers.timezone))
employee_uuid = str(companies_list[0]["uu_id"])
print('employee_uuid', employee_uuid)
set_to_redis_dict = dict(
user=found_user, token=model_value, add_uuid=employee_uuid,
header_info=dict(language=headers.language, domain=headers.domain, timezone=headers.timezone),
)
user_dict = found_user.get_dict()
person_dict = found_user.person.get_dict()
if access_token := RedisHandlers().set_object_to_redis(**set_to_redis_dict):
@ -140,21 +146,12 @@ class LoginHandler:
"access_token": access_token,
"user_type": UserType.employee.name,
"user": {
"uuid": user_dict["uu_id"],
"avatar": user_dict["avatar"],
"email": user_dict["email"],
"phone_number": user_dict["phone_number"],
"user_tag": user_dict["user_tag"],
"uuid": user_dict["uu_id"], "avatar": user_dict["avatar"], "email": user_dict["email"], "phone_number": user_dict["phone_number"], "user_tag": user_dict["user_tag"],
"password_expiry_begins": str(arrow.get(user_dict["password_expiry_begins"]).shift(days=int(user_dict["password_expires_day"]))),
"person": {
"uuid": person_dict["uu_id"],
"firstname": person_dict["firstname"],
"surname": person_dict["surname"],
"middle_name": person_dict["middle_name"],
"sex_code": person_dict["sex_code"],
"person_tag": person_dict["person_tag"],
"country_code": person_dict["country_code"],
"birth_date": person_dict["birth_date"],
"uuid": person_dict["uu_id"], "firstname": person_dict["firstname"], "surname": person_dict["surname"],
"middle_name": person_dict["middle_name"], "sex_code": person_dict["sex_code"], "person_tag": person_dict["person_tag"],
"country_code": person_dict["country_code"], "birth_date": person_dict["birth_date"],
},
},
"selection_list": companies_list,
@ -162,22 +159,21 @@ class LoginHandler:
raise ValueError("Something went wrong")
@classmethod
def do_occupant_login(cls, request: Any, data: Any, db_session, extra_dict: Optional[Dict[str, Any]] = None):
# headers=headers, data=data, db_session=db_session
def do_occupant_login(cls, headers: CommonHeaders, data: Any, db_session):
"""
Handle occupant login.
"""
language = extra_dict.get("language", "tr")
domain = extra_dict.get("domain", None)
timezone = extra_dict.get("tz", None) or "GMT+3"
language = headers.request.headers.get("language", "tr")
domain = headers.request.headers.get("domain", None)
timezone = headers.request.headers.get("tz", None) or "GMT+3"
BuildParts.set_session(db_session)
OccupantTypes.set_session(db_session)
user_handler = UserHandlers()
found_user = user_handler.check_user_exists(
access_key=data.access_key, db_session=db_session
)
found_user = user_handler.check_user_exists(access_key=data.access_key, db_session=db_session)
other_domains_list, main_domain = [], ""
with mongo_handler.collection(
f"{str(found_user.related_company)}*Domain"
) as collection:
with mongo_handler.collection(f"{str(found_user.related_company)}*Domain") as collection:
result = collection.find_one({"user_uu_id": str(found_user.uu_id)})
if not result:
raise ValueError("EYS_00087")
@ -186,78 +182,43 @@ class LoginHandler:
if domain not in other_domains_list or not main_domain:
raise ValueError("EYS_00088")
if not user_handler.check_password_valid(
domain=main_domain,
id_=str(found_user.uu_id),
password=data.password,
password_hashed=found_user.hash_password,
):
if not user_handler.check_password_valid(domain=main_domain, id_=str(found_user.uu_id), password=data.password, password_hashed=found_user.hash_password):
raise ValueError("EYS_0005")
occupants_selection_dict: Dict[str, Any] = {}
living_spaces: list[BuildLivingSpace] = BuildLivingSpace.filter_all(
BuildLivingSpace.person_id == found_user.person_id, db=db_session
).data
living_spaces: list[BuildLivingSpace] = BuildLivingSpace.query.filter(BuildLivingSpace.person_id == found_user.person_id).all()
if not living_spaces:
raise ValueError("EYS_0006")
for living_space in living_spaces:
build_part = BuildParts.filter_one(
BuildParts.id == living_space.build_parts_id,
db=db_session,
).data
build_part = BuildParts.query.filter(BuildParts.id == living_space.build_parts_id).first()
if not build_part:
raise ValueError("EYS_0007")
build = build_part.buildings
occupant_type = OccupantTypes.filter_by_one(
id=living_space.occupant_type_id,
db=db_session,
system=True,
).data
occupant_type = OccupantTypes.query.filter(OccupantTypes.id == living_space.occupant_type_id).first()
occupant_data = {
"build_living_space_uu_id": str(living_space.uu_id),
"part_uu_id": str(build_part.uu_id),
"part_name": build_part.part_name(db=db_session),
"part_level": build_part.part_level,
"occupant_uu_id": str(occupant_type.uu_id),
"description": occupant_type.occupant_description,
"code": occupant_type.occupant_code,
"build_living_space_uu_id": str(living_space.uu_id), "part_uu_id": str(build_part.uu_id), "part_name": build_part.part_name(), "part_level": build_part.part_level,
"occupant_uu_id": str(occupant_type.uu_id), "description": occupant_type.occupant_description, "code": occupant_type.occupant_code,
}
build_key = str(build.uu_id)
if build_key not in occupants_selection_dict:
occupants_selection_dict[build_key] = {
"build_uu_id": build_key,
"build_name": build.build_name,
"build_no": build.build_no,
"occupants": [occupant_data],
}
occupants_selection_dict[build_key] = {"build_uu_id": build_key, "build_name": build.build_name, "build_no": build.build_no, "occupants": [occupant_data],}
else:
occupants_selection_dict[build_key]["occupants"].append(occupant_data)
person = found_user.person
model_value = OccupantTokenObject(
user_type=UserType.occupant.value,
user_uu_id=str(found_user.uu_id),
user_id=found_user.id,
person_id=person.id,
person_uu_id=str(person.uu_id),
domain_list=other_domains_list,
request=dict(request.headers),
available_occupants=occupants_selection_dict,
user_type=UserType.occupant.value, user_uu_id=str(found_user.uu_id), user_id=found_user.id, person_id=person.id,
person_uu_id=str(person.uu_id), domain_list=other_domains_list, request=dict(request.headers), available_occupants=occupants_selection_dict,
).model_dump()
redis_handler = RedisHandlers()
if access_token := redis_handler.set_object_to_redis(
user=found_user,
token=model_value,
header_info=dict(language=language, domain=domain, timezone=timezone),
user=found_user, token=model_value, add_uuid=living_space.uu_id,
header_info=dict(language=language, domain=domain, timezone=timezone)
):
return {
"access_token": access_token,
"user_type": UserType.occupant.name,
"selection_list": occupants_selection_dict,
}
return {"access_token": access_token, "user_type": UserType.occupant.name, "selection_list": occupants_selection_dict}
raise ValueError("Something went wrong")
@classmethod
@ -302,7 +263,7 @@ class LoginHandler:
@classmethod
def handle_employee_selection(cls, access_token: str, data: Any, token_dict: TokenDictType):
with Users.new_session() as db_session:
if data.company_uu_id not in token_dict.companies_uu_id_list:
if data.uuid not in token_dict.companies_uu_id_list:
ValueError("EYS_0011")
list_of_returns = (
Employees.id, Employees.uu_id, People.id, People.uu_id, Users.id, Users.uu_id, Companies.id, Companies.uu_id,
@ -319,7 +280,7 @@ class LoginHandler:
).join(Companies, Companies.id == Departments.company_id
).join(Users, Users.person_id == People.id
).outerjoin(Addresses, Addresses.id == Companies.official_address_id
).filter(Companies.uu_id == data.company_uu_id, Users.id == token_dict.user_id)
).filter(Employees.uu_id == data.uuid, Users.id == token_dict.user_id)
selected_company_first = selected_company_query.first()
if not selected_company_first:
@ -357,8 +318,10 @@ class LoginHandler:
reachable_app_codes=reachable_app_codes,
)
redis_handler = RedisHandlers()
redis_result = redis_handler.update_token_at_redis(token=access_token, add_payload=company_token)
return {"selected_uu_id": data.company_uu_id}
redis_result = redis_handler.update_token_at_redis(
token=access_token, add_payload=company_token, add_uuid=str(result_with_keys_dict['Employees.uu_id'])
)
return {"selected_uu_id": data.uuid}
@classmethod
def handle_occupant_selection(cls, access_token: str, data: Any, token_dict: TokenDictType):
@ -408,7 +371,9 @@ class LoginHandler:
reachable_app_codes=reachable_app_codes,
)
redis_handler = RedisHandlers()
redis_handler.update_token_at_redis(token=access_token, add_payload=occupant_token)
redis_handler.update_token_at_redis(
token=access_token, add_payload=occupant_token, add_uuid=occupant_token.living_space_uu_id
)
return {"selected_uu_id": occupant_token.living_space_uu_id}
@classmethod # Requires auth context

View File

@ -15,16 +15,7 @@ class RequestVerifyOTP(BaseModel):
class RequestSelectEmployee(BaseModel):
company_uu_id: str
@property
def is_employee(self):
return True
@property
def is_occupant(self):
return False
uuid: str
class RequestResetPassword(BaseModel):
@ -34,17 +25,7 @@ class RequestResetPassword(BaseModel):
class RequestSelectLiving(BaseModel):
build_living_space_uu_id: str
@property
def is_employee(self):
return False
@property
def is_occupant(self):
return True
uuid: str
class RequestCreatePassword(BaseModel):
password_token: str

View File

@ -22,7 +22,9 @@ def authentication_page_valid(data: RequestApplication, headers: CommonHeaders =
Verify if page is valid returns application that can user reach
page: { url = /building/create} | result: { "application": "4c11f5ef-0bbd-41ac-925e-f79d9aac2b0e" }
"""
return PageHandlers.retrieve_valid_page_via_token(access_token=headers.token, page_url=data.page)
list_of = PageHandlers.retrieve_valid_page_via_token(access_token=headers.token, page_url=data.page)
print('list_of', list_of)
return {"completed": True, "application": list_of}
application_retrieve_all_sites = "ApplicationRetrieveAllSites"
@ -36,4 +38,6 @@ def authentication_get_all_sites_list(headers: CommonHeaders = Depends(CommonHea
"""
Verify if page is valid returns application that can user reach result: { "sites": ['/dashboard', '/building/create'] }
"""
return PageHandlers.retrieve_valid_sites_via_token(access_token=headers.token)
list_of_application_url = PageHandlers.retrieve_valid_sites_via_token(access_token=headers.token)
print('list_of_application_url', list(list_of_application_url))
return {"completed": True, "sites": list(list_of_application_url)}

View File

@ -27,16 +27,14 @@ class TokenProvider:
raise ValueError("Invalid user type")
@classmethod
def get_dict_from_redis(cls, token: Optional[str] = None, user_uu_id: Optional[str] = None) -> Union[TokenDictType, List[TokenDictType]]:
def get_dict_from_redis(
cls, token: Optional[str] = None, user_uu_id: Optional[str] = None
) -> Union[TokenDictType, List[TokenDictType]]:
"""
Retrieve token object from Redis using token and user_uu_id
"""
token_to_use, user_uu_id_to_use = token or "*", user_uu_id or "*"
list_of_token_dict, auth_key_list = [], [
cls.AUTH_TOKEN,
token_to_use,
user_uu_id_to_use,
]
list_of_token_dict, auth_key_list = [], [cls.AUTH_TOKEN, token_to_use, user_uu_id_to_use, "*"]
if token:
result = RedisActions.get_json(list_keys=auth_key_list, limit=1)
if first_record := result.first:
@ -47,9 +45,7 @@ class TokenProvider:
for all_record in all_records:
list_of_token_dict.append(cls.convert_redis_object_to_token(all_record))
return list_of_token_dict
raise ValueError(
"Token not found in Redis. Please check the token or user_uu_id."
)
raise ValueError("Token not found in Redis. Please check the token or user_uu_id.")
@classmethod
def retrieve_application_codes(cls, page_url: str, token: TokenDictType):

View File

@ -33,7 +33,7 @@ class RedisHandlers:
@classmethod
def get_object_from_redis(cls, access_token: str) -> TokenDictType:
redis_response = RedisActions.get_json(list_keys=[cls.AUTH_TOKEN, access_token, "*"])
redis_response = RedisActions.get_json(list_keys=[cls.AUTH_TOKEN, access_token, "*", "*"])
if not redis_response.status:
raise ValueError("EYS_0001")
if redis_object := redis_response.first:
@ -41,22 +41,30 @@ class RedisHandlers:
raise ValueError("EYS_0002")
@classmethod
def set_object_to_redis(cls, user: Users, token, header_info):
result_delete = RedisActions.delete(list_keys=[cls.AUTH_TOKEN, "*", str(user.uu_id)])
def set_object_to_redis(cls, user: Users, token, header_info, add_uuid: str):
result_delete = RedisActions.delete(list_keys=[cls.AUTH_TOKEN, "*", str(user.uu_id), add_uuid])
generated_access_token = PasswordModule.generate_access_token()
keys = [cls.AUTH_TOKEN, generated_access_token, str(user.uu_id)]
if add_uuid:
keys.append(add_uuid)
RedisActions.set_json(list_keys=keys, value={**token, **header_info}, expires={"hours": 1, "minutes": 30})
return generated_access_token
RedisActions.set_json(list_keys=keys, value={**token, **header_info}, expires={"hours": 1, "minutes": 30})
return generated_access_token
@classmethod
def update_token_at_redis(cls, token: str, add_payload: Union[CompanyToken, OccupantToken]):
if already_token_data := RedisActions.get_json(list_keys=[cls.AUTH_TOKEN, token, "*"]).first:
def update_token_at_redis(cls, token: str, add_payload: Union[CompanyToken, OccupantToken], add_uuid: str):
if already_token_data := RedisActions.get_json(list_keys=[cls.AUTH_TOKEN, token, '*', add_uuid]).first:
already_token = cls.process_redis_object(already_token_data)
if already_token.is_employee and isinstance(add_payload, CompanyToken):
already_token.selected_company = add_payload
list_keys = [cls.AUTH_TOKEN, token, str(already_token.user_uu_id), str(add_uuid)]
print('is_employee: ', list_keys)
elif already_token.is_occupant and isinstance(add_payload, OccupantToken):
already_token.selected_occupant = add_payload
list_keys = [cls.AUTH_TOKEN, token, str(already_token.user_uu_id)]
list_keys = [cls.AUTH_TOKEN, token, str(already_token.user_uu_id), str(add_uuid)]
print('is_occupant: ', list_keys)
result = RedisActions.set_json(list_keys=list_keys, value=already_token.model_dump(), expires={"hours": 1, "minutes": 30})
RedisActions.delete(list_keys=[cls.AUTH_TOKEN, token, str(already_token.user_uu_id)])
return result.first
raise ValueError("Something went wrong")

View File

@ -1,20 +1,19 @@
services:
# client_frontend:
# container_name: client_frontend
# build:
# context: .
# dockerfile: web_services/client_frontend/Dockerfile
# networks:
# - wag-services
# ports:
# - "3000:3000"
# environment:
# - NODE_ENV=development
# - WEB_BASE_URL=http://localhost:3000
# - API_BASE_URL=http://localhost:3000/api
# cpus: 1.5
# mem_limit: 2048m
client_frontend:
container_name: client_frontend
build:
context: .
dockerfile: web_services/client_frontend/Dockerfile
networks:
- wag-services
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- WEB_BASE_URL=http://localhost:3000
- API_BASE_URL=http://localhost:3000/api
cpus: 1.5
mem_limit: 2048m
# management_frontend:
# container_name: management_frontend

View File

@ -39,6 +39,7 @@
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"flatpickr": "^4.6.13",
"ioredis": "^5.6.1",
"lucide-react": "^0.487.0",
"next": "^15.2.4",
"next-crypto": "^1.0.8",
@ -739,6 +740,12 @@
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
"license": "MIT"
},
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
@ -3700,6 +3707,15 @@
"node": ">=6"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/cmdk": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz",
@ -3922,7 +3938,6 @@
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@ -3979,6 +3994,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -5358,6 +5382,30 @@
"node": ">= 0.4"
}
},
"node_modules/ioredis": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz",
"integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==",
"license": "MIT",
"dependencies": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@ -6181,6 +6229,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"license": "MIT"
},
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -6366,7 +6426,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
@ -7096,6 +7155,27 @@
}
}
},
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"license": "MIT",
"dependencies": {
"redis-errors": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@ -7605,6 +7685,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
"license": "MIT"
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",

View File

@ -40,6 +40,7 @@
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"flatpickr": "^4.6.13",
"ioredis": "^5.6.1",
"lucide-react": "^0.487.0",
"next": "^15.2.4",
"next-crypto": "^1.0.8",

View File

@ -0,0 +1,153 @@
"use server";
import { retrieveAccessToken } from "@/apifetchers/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<never> => {
return new Promise((_, reject) => {
setTimeout(() => {
controller.abort();
reject(new Error(`Request timed out after ${ms}ms`));
}, ms);
});
};
/**
* 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<T>(
url: string,
options: FetchOptions = {},
headers: Record<string, string> = defaultHeaders,
payload?: any
): Promise<ApiResponse<T>> {
const { method = "POST", cache = false, timeout = DEFAULT_TIMEOUT } = options;
try {
// Setup controller for timeout handling
const controller = new AbortController();
// Prepare fetch options
const fetchOptions: RequestInit = {
method,
headers,
cache: cache ? "force-cache" : "no-cache",
signal: controller.signal,
};
// Add body for non-GET requests with payload
if (method !== "GET" && payload) {
fetchOptions.body = JSON.stringify(
payload.payload ? payload.payload : payload
);
}
// Create timeout promise
const timeoutPromise = createTimeoutPromise(timeout, controller);
// Execute request with timeout
const response = await Promise.race([
fetch(url, fetchOptions),
timeoutPromise,
]);
// Parse response
const responseData = await response.json();
// Return standardized response
return {
status: response.status,
data: responseData || ({} as T),
};
} catch (error) {
console.error(`API Error (${url}):`, error);
return {
...DEFAULT_RESPONSE,
error: error instanceof Error ? error.message : "Network error",
} as ApiResponse<T>;
}
}
/**
* Fetch data without authentication
*/
async function fetchData<T>(
endpoint: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
return coreFetch<T>(
endpoint,
{ method, cache, timeout },
defaultHeaders,
payload
);
}
/**
* Fetch data with authentication token
*/
async function fetchDataWithToken<T>(
endpoint: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
const accessToken = (await retrieveAccessToken()) || "";
const headers = {
...defaultHeaders,
"eys-acs-tkn": accessToken,
};
return coreFetch<T>(endpoint, { method, cache, timeout }, headers, payload);
}
/**
* Update data with authentication token and UUID
*/
async function updateDataWithToken<T>(
endpoint: string,
uuid: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
const accessToken = (await retrieveAccessToken()) || "";
const headers = {
...defaultHeaders,
"eys-acs-tkn": accessToken,
};
return coreFetch<T>(
`${endpoint}/${uuid}`,
{ method, cache, timeout },
headers,
payload
);
}
export { fetchData, fetchDataWithToken, updateDataWithToken };

View File

@ -0,0 +1,80 @@
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 baseUrlRestriction = formatServiceUrl(
process.env.NEXT_PUBLIC_RESTRICTION_SERVICE_URL || "restriction_service:8002"
);
const baseUrlApplication = formatServiceUrl(
process.env.NEXT_PUBLIC_MANAGEMENT_SERVICE_URL || "management_service:8003"
);
const baseUrlAccount = formatServiceUrl(
process.env.NEXT_PUBLIC_ACCOUNT_SERVICE_URL || "account_service:8004"
);
const baseUrlBuilding = formatServiceUrl(
process.env.NEXT_PUBLIC_BUILDING_SERVICE_URL || "building_service:8006"
);
const baseUrlPeople = formatServiceUrl(
process.env.NEXT_PUBLIC_VALIDATION_SERVICE_URL || "validation_service:8009"
);
// Types for better type safety
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
interface ApiResponse<T = any> {
status: number;
data: T;
error?: string;
}
interface FetchOptions {
method?: HttpMethod;
cache?: boolean;
timeout?: number;
}
const tokenSecret =
process.env.TOKENSECRET_90 || "787a4a44-2a27-454d-8918-35ceab50592d";
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: "evyos.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,
baseUrlAccount,
baseUrlBuilding,
baseUrlRestriction,
tokenSecret,
cookieObject,
};

View File

@ -0,0 +1,218 @@
"use server";
import NextCrypto from "next-crypto";
import { fetchData, fetchDataWithToken } from "@/apifetchers/api-fetcher";
import { baseUrlAuth, cookieObject, tokenSecret } from "@/apifetchers/basics";
import { cookies } from "next/headers";
import { setNewCompleteToRedis, getCompleteFromRedis } from "@/apifetchers/mutual/context/complete/fetch";
import {
defaultClientHeader,
defaultClientMenu,
defaultClientOnline,
defaultClientPageConfig,
defaultLinkList
} from "@/types/mutual/context/validations";
import { retrievePageList } from "@/apifetchers/mutual/cookies/token";
import { setMenuToRedis } from "@/apifetchers/mutual/context/page/menu/fetch";
const loginEndpoint = `${baseUrlAuth}/authentication/login`;
const loginSelectEndpoint = `${baseUrlAuth}/authentication/select`;
const logoutEndpoint = `${baseUrlAuth}/authentication/logout`;
interface LoginViaAccessKeys {
accessKey: string;
password: string;
rememberMe: boolean;
}
interface LoginSelect {
uuid: string;
}
interface LoginSelectOccupant {
uuid: any;
}
async function logoutActiveSession() {
const cookieStore = await cookies();
const response = await fetchDataWithToken(logoutEndpoint, {}, "GET", false);
cookieStore.delete("eys-zzz");
cookieStore.delete("eys-yyy");
cookieStore.delete("eys-sel");
return response;
}
async function initRedis(loginRespone: any, firstSelection: any, accessToken: string, redisKey: string) {
let alreadyAtRedis = null
try {
const redisData = await getCompleteFromRedis();
alreadyAtRedis = redisData;
} catch (error) { }
if (!alreadyAtRedis) {
const loginObjectToRedis = {
online: {
...defaultClientOnline,
lastLogin: new Date(),
userType: `${loginRespone.user_type}`.toUpperCase(),
lang: loginRespone.user.person.country_code.toLowerCase(),
},
pageConfig: defaultClientPageConfig,
menu: defaultClientMenu,
header: defaultClientHeader,
selection: { selectionList: loginRespone.selection_list, activeSelection: firstSelection },
user: loginRespone.user,
settings: { lastOnline: new Date(), token: accessToken },
chatRoom: defaultLinkList,
notifications: defaultLinkList,
messages: defaultLinkList,
}
await setNewCompleteToRedis(loginObjectToRedis, redisKey);
}
}
async function loginViaAccessKeys(payload: LoginViaAccessKeys) {
const cookieStore = await cookies();
try {
const response = await fetchData(
loginEndpoint,
{
access_key: payload.accessKey,
password: payload.password,
remember_me: payload.rememberMe,
},
"POST",
false
);
if (response.status === 200 || response.status === 202) {
const loginRespone: any = response?.data;
const firstSelection = loginRespone.selection_list.find((item: any) => item.uu_id === loginRespone.selection_list[0].uu_id);
const nextCrypto = new NextCrypto(tokenSecret);
const accessToken = await nextCrypto.encrypt(loginRespone.access_token);
const redisKey = `CLIENT:${loginRespone.user_type.toUpperCase()}:${firstSelection.uu_id}`;
const redisKeyAccess = await nextCrypto.encrypt(redisKey);
const usersSelection = await nextCrypto.encrypt(
JSON.stringify({
selected: firstSelection.uu_id,
userType: loginRespone.user_type.toUpperCase(),
redisKey,
})
);
await initRedis(loginRespone, firstSelection, accessToken, redisKey);
cookieStore.set({
name: "eys-zzz",
value: accessToken,
...cookieObject,
});
cookieStore.set({
name: "eys-yyy",
value: redisKeyAccess,
...cookieObject,
});
cookieStore.set({
name: "eys-sel",
value: usersSelection,
...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: LoginSelect) {
const cookieStore = await cookies();
const nextCrypto = new NextCrypto(tokenSecret);
const employeeUUID = payload.uuid;
const redisKey = `CLIENT:EMPLOYEE:${employeeUUID}`;
const selectResponse: any = await fetchDataWithToken(loginSelectEndpoint, { uuid: employeeUUID }, "POST", false);
cookieStore.delete("eys-sel");
if (selectResponse.status === 200 || selectResponse.status === 202) {
const usersSelection = await nextCrypto.encrypt(
JSON.stringify({
selected: employeeUUID,
userType: "employee",
redisKey,
})
);
cookieStore.set({
name: "eys-sel",
value: usersSelection,
...cookieObject,
});
try {
const pageList = await retrievePageList()
await setMenuToRedis({
selectionList: pageList,
activeSelection: "/dashboard"
})
} catch (error) { }
}
return selectResponse;
}
async function loginSelectOccupant(payload: LoginSelectOccupant) {
const livingSpaceUUID = payload.uuid;
const cookieStore = await cookies();
const nextCrypto = new NextCrypto(tokenSecret);
const selectResponse: any = await fetchDataWithToken(loginSelectEndpoint, { uuid: livingSpaceUUID }, "POST", false);
const redisKey = `CLIENT:OCCUPANT:${livingSpaceUUID}`;
cookieStore.delete("eys-sel");
if (selectResponse.status === 200 || selectResponse.status === 202) {
const usersSelection = await nextCrypto.encrypt(
JSON.stringify({
selected: livingSpaceUUID,
userType: "OCCUPANT",
redisKey,
})
);
cookieStore.set({
name: "eys-sel",
value: usersSelection,
...cookieObject,
});
}
return selectResponse;
}
export {
loginViaAccessKeys,
loginSelectEmployee,
loginSelectOccupant,
logoutActiveSession,
};

View File

@ -0,0 +1,32 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/apifetchers/utils";
import { ClientRedisToken } from "@/types/mutual/context/validations";
const getCompleteFromRedis = async (): Promise<ClientRedisToken> => {
const decrpytUserSelection = await functionRetrieveUserSelection()
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new Error("No redis key found");
const result = await redis.get(`${redisKey}`);
if (!result) throw new Error("No data found in redis");
return JSON.parse(result);
}
const setCompleteToRedis = async (completeObject: ClientRedisToken) => {
const decrpytUserSelection = await functionRetrieveUserSelection()
if (!decrpytUserSelection) throw new Error("No user selection found");
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new Error("No redis key found");
if (!completeObject) throw new Error("No complete object provided");
await redis.set(redisKey, JSON.stringify(completeObject));
return true;
}
const setNewCompleteToRedis = async (completeObject: ClientRedisToken, redisKey: string) => {
if (!redisKey) throw new Error("No redis key found");
if (!completeObject) throw new Error("No complete object provided");
await redis.set(redisKey, JSON.stringify(completeObject));
return true;
}
export { getCompleteFromRedis, setCompleteToRedis, setNewCompleteToRedis };

View File

@ -0,0 +1,32 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/apifetchers/utils";
import { ClientSelection } from "@/types/mutual/context/validations";
import { getCompleteFromRedis, setCompleteToRedis } from "@/apifetchers/mutual/context/complete/fetch";
const getSelectionFromRedis = async (): Promise<ClientSelection> => {
try {
const decrpytUserSelection = await functionRetrieveUserSelection();
const redisKey = decrpytUserSelection?.redisKey;
if (redisKey === "default") { return { selectionList: [], activeSelection: {} } }
const result = await redis.get(`${redisKey}`);
if (!result) { return { selectionList: [], activeSelection: {} } }
const parsedResult = JSON.parse(result);
if (!parsedResult.selection) { return { selectionList: [], activeSelection: {} } }
return parsedResult.selection;
} catch (error) { return { selectionList: [], activeSelection: {} } }
}
const setSelectionToRedis = async (selectionObject: ClientSelection) => {
const decrpytUserSelection = await functionRetrieveUserSelection()
if (!decrpytUserSelection) throw new Error("No user selection found");
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new Error("No redis key found");
if (!selectionObject) throw new Error("No selection object provided");
const oldData = await getCompleteFromRedis();
if (!oldData) throw new Error("No old data found in redis");
await setCompleteToRedis({ ...oldData, selection: selectionObject })
return true;
}
export { getSelectionFromRedis, setSelectionToRedis };

View File

@ -0,0 +1,30 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/apifetchers/utils";
import { ClientSettings } from "@/types/mutual/context/validations";
import { getCompleteFromRedis, setCompleteToRedis } from "@/apifetchers/mutual/context/complete/fetch";
const getSettingsFromRedis = async (): Promise<ClientSettings> => {
const decrpytUserSelection = await functionRetrieveUserSelection()
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new Error("No redis key found");
const result = await redis.get(`${redisKey}`);
if (!result) throw new Error("No data found in redis");
const parsedResult = JSON.parse(result);
if (!parsedResult.settings) throw new Error("No settings found in redis");
return parsedResult.settings;
}
const setSettingsToRedis = async (settingsObject: ClientSettings) => {
const decrpytUserSelection = await functionRetrieveUserSelection()
if (!decrpytUserSelection) throw new Error("No user selection found");
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new Error("No redis key found");
if (!settingsObject) throw new Error("No settings object provided");
const oldData = await getCompleteFromRedis();
if (!oldData) throw new Error("No old data found in redis");
await setCompleteToRedis({ ...oldData, settings: settingsObject })
return true;
}
export { getSettingsFromRedis, setSettingsToRedis };

View File

@ -0,0 +1,30 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/apifetchers/utils";
import { ClientUser } from "@/types/mutual/context/validations";
import { getCompleteFromRedis, setCompleteToRedis } from "@/apifetchers/mutual/context/complete/fetch";
const getUserFromRedis = async (): Promise<ClientUser> => {
const decrpytUserSelection = await functionRetrieveUserSelection()
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new Error("No redis key found");
const result = await redis.get(`${redisKey}`);
if (!result) throw new Error("No data found in redis");
const parsedResult = JSON.parse(result);
if (!parsedResult.user) throw new Error("No user found in redis");
return parsedResult.user;
}
const setUserToRedis = async (userObject: ClientUser) => {
const decrpytUserSelection = await functionRetrieveUserSelection()
if (!decrpytUserSelection) throw new Error("No user selection found");
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new Error("No redis key found");
if (!userObject) throw new Error("No user object provided");
const oldData = await getCompleteFromRedis();
if (!oldData) throw new Error("No old data found in redis");
await setCompleteToRedis({ ...oldData, user: userObject });
return true;
}
export { getUserFromRedis, setUserToRedis };

View File

@ -0,0 +1,31 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/apifetchers/utils";
import { ClientSettings } from "@/types/mutual/context/validations";
import { getCompleteFromRedis, setCompleteToRedis } from "@/apifetchers/mutual/context/complete/fetch";
const getConfigFromRedis = async (): Promise<ClientSettings> => {
const decrpytUserSelection = await functionRetrieveUserSelection()
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new Error("No redis key found");
const result = await redis.get(`${redisKey}`);
if (!result) throw new Error("No data found in redis");
const parsedResult = JSON.parse(result);
if (!parsedResult.settings) throw new Error("No settings found in redis");
return parsedResult.settings;
}
const setConfigToRedis = async (settingsObject: ClientSettings) => {
const decrpytUserSelection = await functionRetrieveUserSelection()
if (!decrpytUserSelection) throw new Error("No user selection found");
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new Error("No redis key found");
if (!settingsObject) throw new Error("No settings object provided");
const oldData = await getCompleteFromRedis();
if (!oldData) throw new Error("No old data found in redis");
await setCompleteToRedis({ ...oldData, settings: settingsObject });
return true;
}
export { getConfigFromRedis, setConfigToRedis };

View File

@ -0,0 +1,30 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/apifetchers/utils";
import { ClientMenu } from "@/types/mutual/context/validations";
import { getCompleteFromRedis, setCompleteToRedis } from "@/apifetchers/mutual/context/complete/fetch";
const getMenuFromRedis = async (): Promise<ClientMenu> => {
const decrpytUserSelection = await functionRetrieveUserSelection()
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new Error("No redis key found");
const result = await redis.get(`${redisKey}`);
if (!result) throw new Error("No data found in redis");
const parsedResult = JSON.parse(result);
if (!parsedResult.menu) throw new Error("No menu found in redis");
return parsedResult.menu;
}
const setMenuToRedis = async (menuObject: ClientMenu) => {
const decrpytUserSelection = await functionRetrieveUserSelection()
if (!decrpytUserSelection) throw new Error("No user selection found");
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new Error("No redis key found");
if (!menuObject) throw new Error("No menu object provided");
const oldData = await getCompleteFromRedis();
if (!oldData) throw new Error("No old data found in redis");
await setCompleteToRedis({ ...oldData, menu: menuObject });
return true;
}
export { getMenuFromRedis, setMenuToRedis };

View File

@ -0,0 +1,82 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/apifetchers/utils";
import { ClientOnline } from "@/types/mutual/context/validations";
import { getCompleteFromRedis, setCompleteToRedis } from "@/apifetchers/mutual/context/complete/fetch";
const getOnlineFromRedis = async (): Promise<ClientOnline> => {
try {
// Get user selection with default fallback
const decrpytUserSelection = await functionRetrieveUserSelection();
const redisKey = decrpytUserSelection?.redisKey;
// If we have a default redisKey, return a default online object
if (redisKey === "default") {
return {
lang: "en",
userType: "occupant",
lastLogin: new Date(),
lastLogout: new Date(),
lastAction: new Date(),
lastPage: "/auth/login",
timezone: "GMT+3"
};
}
// Try to get data from Redis
const result = await redis.get(`${redisKey}`);
if (!result) {
return {
lang: "en",
userType: "occupant",
lastLogin: new Date(),
lastLogout: new Date(),
lastAction: new Date(),
lastPage: "/auth/login",
timezone: "GMT+3"
};
}
// Parse the result
const parsedResult = JSON.parse(result);
if (!parsedResult.online) {
return {
lang: "en",
userType: "occupant",
lastLogin: new Date(),
lastLogout: new Date(),
lastAction: new Date(),
lastPage: "/auth/login",
timezone: "GMT+3"
};
}
return parsedResult.online;
} catch (error) {
console.error("Error getting online from Redis:", error);
// Return default online object in case of any error
return {
lang: "en",
userType: "occupant",
lastLogin: new Date(),
lastLogout: new Date(),
lastAction: new Date(),
lastPage: "/auth/login",
timezone: "GMT+3"
};
}
}
const setOnlineToRedis = async (onlineObject: ClientOnline) => {
const decrpytUserSelection = await functionRetrieveUserSelection()
if (!decrpytUserSelection) throw new Error("No user selection found");
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new Error("No redis key found");
if (!onlineObject) throw new Error("No online object provided");
const oldData = await getCompleteFromRedis();
if (!oldData) throw new Error("No old data found in redis");
await setCompleteToRedis({ ...oldData, online: onlineObject });
return true;
}
export { getOnlineFromRedis, setOnlineToRedis };

View File

@ -0,0 +1,57 @@
"use server";
import NextCrypto from "next-crypto";
import { fetchDataWithToken } from "@/apifetchers/api-fetcher";
import { baseUrlAuth, baseUrlRestriction, tokenSecret } from "@/apifetchers/basics";
import { cookies } from "next/headers";
const checkToken = `${baseUrlAuth}/authentication/token/check`;
const pageValid = `${baseUrlRestriction}/restrictions/page/valid`;
const siteUrls = `${baseUrlRestriction}/restrictions/sites/list`;
const nextCrypto = new NextCrypto(tokenSecret);
function fetchResponseStatus(response: any) {
return 199 < response?.status && response?.status < 300;
}
async function checkAccessTokenIsValid() {
try {
const response = await fetchDataWithToken(checkToken, {}, "GET", false);
return fetchResponseStatus(response) ? true : false;
} catch (error) {
console.error("Error checking token validity:", error);
return false;
}
}
async function retrievePageList() {
const response: any = await fetchDataWithToken(siteUrls, {}, "GET", false);
return fetchResponseStatus(response) ? response.data?.sites : null;
}
async function retrieveApplicationbyUrl(pageUrl: string) {
const response: any = await fetchDataWithToken(pageValid, { page_url: pageUrl }, "POST", false);
return fetchResponseStatus(response) ? response.data?.application : null;
}
async function retrieveAccessToken() {
const cookieStore = await cookies();
const encrpytAccessToken = cookieStore.get("eys-zzz")?.value || "";
return encrpytAccessToken ? await nextCrypto.decrypt(encrpytAccessToken) : null;
}
async function retrieveAccessObjects() {
const cookieStore = await cookies();
const encrpytAccessObject = cookieStore.get("eys-yyy")?.value || "";
const decrpytAccessObject = await nextCrypto.decrypt(encrpytAccessObject);
return decrpytAccessObject ? JSON.parse(decrpytAccessObject) : null;
}
export {
checkAccessTokenIsValid,
retrieveAccessToken,
retrieveAccessObjects,
retrieveApplicationbyUrl,
retrievePageList,
};

View File

@ -0,0 +1,33 @@
"use server";
import NextCrypto from "next-crypto";
import { cookieObject, tokenSecret } from "@/apifetchers/basics";
import { cookies } from "next/headers";
const nextCrypto = new NextCrypto(tokenSecret);
const functionRetrieveUserSelection = async () => {
const cookieStore = await cookies();
const encrpytUserSelection = cookieStore.get("eys-sel")?.value || "";
if (!encrpytUserSelection) throw new Error("No user selection found");
const decrpytUserSelection = await nextCrypto.decrypt(encrpytUserSelection);
if (!decrpytUserSelection) throw new Error("No user selection found");
return JSON.parse(decrpytUserSelection);
}
const functionSetUserSelection = async (userSelection: any) => {
const cookieStore = await cookies();
const encrpytUserSelection = await nextCrypto.encrypt(JSON.stringify(userSelection));
if (!encrpytUserSelection) throw new Error("No user selection found");
cookieStore.set({
name: "eys-sel",
value: encrpytUserSelection,
...cookieObject,
});
}
const functionRemoveUserSelection = async () => {
const cookieStore = await cookies();
cookieStore.delete("eys-sel");
}
export { functionRetrieveUserSelection, functionSetUserSelection, functionRemoveUserSelection };

View File

@ -0,0 +1,17 @@
'use server';
import { AuthLayout } from "@/layouts/auth/layout";
import { AuthServerProps } from "@/validations/mutual/pages/props";
import { checkContextPageOnline } from "@/components/mutual/context/online/context";
import getPage from "@/webPages/getPage";
const AuthPageEn = async ({ params, searchParams }: AuthServerProps) => {
const online = await checkContextPageOnline();
const lang = online?.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 <AuthLayout lang={lang} page={FoundPage} activePageUrl={pageUrlFromParams} />
}
export default AuthPageEn;

View File

@ -0,0 +1,33 @@
import { NextResponse } from "next/server";
import { retrieveAccessToken } from "@/apifetchers/mutual/cookies/token";
export async function GET() {
try {
// Check if token exists
const token = await retrieveAccessToken();
if (!token) {
return NextResponse.json({
status: 401,
data: null,
error: "No token found",
});
}
// In a real implementation, you would validate the token
// For now, just return success if a token exists
return NextResponse.json({
status: 200,
data: {
valid: true,
},
});
} catch (error) {
console.error("Error checking token:", error);
return NextResponse.json({
status: 500,
data: null,
error: "Error validating token",
});
}
}

View File

@ -0,0 +1,22 @@
import {
getSelectionFromRedis,
setSelectionToRedis,
} from "@/apifetchers/mutual/context/dash/selection/fetch";
import { NextResponse } from "next/server";
export async function GET() {
const selection = await getSelectionFromRedis();
return NextResponse.json({
status: 200,
data: selection || null,
});
}
export async function POST(request: Request) {
const selection = await request.json();
await setSelectionToRedis(selection);
return NextResponse.json({
status: 200,
data: selection || null,
});
}

View File

@ -0,0 +1,16 @@
import {
getSettingsFromRedis,
setSettingsToRedis,
} from "@/apifetchers/mutual/context/dash/settings/fetch";
import { NextResponse } from "next/server";
export async function GET() {
const settings = await getSettingsFromRedis();
return NextResponse.json(settings);
}
export async function POST(request: Request) {
const settings = await request.json();
await setSettingsToRedis(settings);
return NextResponse.json(settings);
}

View File

@ -0,0 +1,16 @@
import {
getUserFromRedis,
setUserToRedis,
} from "@/apifetchers/mutual/context/dash/user/fetch";
import { NextResponse } from "next/server";
export async function GET() {
const user = await getUserFromRedis();
return NextResponse.json(user);
}
export async function POST(request: Request) {
const user = await request.json();
await setUserToRedis(user);
return NextResponse.json(user);
}

View File

@ -0,0 +1,16 @@
import {
getConfigFromRedis,
setConfigToRedis,
} from "@/apifetchers/mutual/context/page/config/fetch";
import { NextResponse } from "next/server";
export async function GET() {
const config = await getConfigFromRedis();
return NextResponse.json(config);
}
export async function POST(request: Request) {
const config = await request.json();
await setConfigToRedis(config);
return NextResponse.json(config);
}

View File

@ -0,0 +1,22 @@
import {
getMenuFromRedis,
setMenuToRedis,
} from "@/apifetchers/mutual/context/page/menu/fetch";
import { NextResponse } from "next/server";
export async function GET() {
const menu = await getMenuFromRedis();
return NextResponse.json({
status: 200,
data: menu,
});
}
export async function POST(request: Request) {
const menu = await request.json();
await setMenuToRedis(menu);
return NextResponse.json({
status: 200,
data: menu,
});
}

View File

@ -0,0 +1,22 @@
import {
getOnlineFromRedis,
setOnlineToRedis,
} from "@/apifetchers/mutual/context/page/online/fetch";
import { NextResponse } from "next/server";
export async function GET() {
const online = await getOnlineFromRedis();
return NextResponse.json({
status: 200,
data: online,
});
}
export async function POST(request: Request) {
const online = await request.json();
await setOnlineToRedis(online);
return NextResponse.json({
status: 200,
data: online,
});
}

View File

@ -1,11 +1,10 @@
import { loginViaAccessKeys } from "@/apicalls/custom/login/login";
import { NextResponse } from "next/server";
import { loginViaAccessKeys } from "@/apifetchers/custom/login/login";
import { loginSchemaEmail } from "@/webPages/auth/login/schemas";
export async function POST(req: Request): Promise<NextResponse> {
try {
const headers = req.headers;
console.log("headers", Object.entries(headers));
const body = await req.json();
const dataValidated = {
accessKey: body.email,

View File

@ -1,20 +1,16 @@
import { API_BASE_URL } from "@/config/config";
import { NextResponse } from "next/server";
export async function POST() {
async function retrieveAvailableApplication(): Promise<string[]> {
return new Promise((resolve) => {
const mockList = [
"management/account/tenant/something",
"management/account/tenant/somethingSecond",
"building/parts/tenant/something",
];
resolve(mockList);
});
}
const availableApplications = await retrieveAvailableApplication();
return NextResponse.json({
status: 200,
data: availableApplications,
const result = await fetch(`${API_BASE_URL}/context/page/menu`, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
try {
const data = await result.json();
return NextResponse.json({ status: 200, data: data });
} catch (error) {
console.log(error);
return NextResponse.json({ status: 500, message: "No data is found" });
}
}

View File

@ -1,9 +1,9 @@
import { z } from "zod";
import { loginSelectEmployee } from "@/apicalls/custom/login/login";
import { loginSelectEmployee } from "@/apifetchers/custom/login/login";
import { NextResponse } from "next/server";
const loginSchemaEmployee = z.object({
company_uu_id: z.string(),
uuid: z.string(),
});
export async function POST(req: Request): Promise<NextResponse> {
@ -12,7 +12,7 @@ export async function POST(req: Request): Promise<NextResponse> {
console.log("headers", Object.entries(headers));
const body = await req.json();
const dataValidated = {
company_uu_id: body.company_uu_id,
uuid: body.uuid,
};
const validatedLoginBody = loginSchemaEmployee.safeParse(body);
if (!validatedLoginBody.success) {

View File

@ -1,18 +1,17 @@
import { z } from "zod";
import { loginSelectOccupant } from "@/apicalls/custom/login/login";
import { loginSelectOccupant } from "@/apifetchers/custom/login/login";
import { NextResponse } from "next/server";
const loginSchemaOccupant = z.object({
build_living_space_uu_id: z.string(),
uuid: z.string(),
});
export async function POST(req: Request): Promise<NextResponse> {
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,
uuid: body.uuid,
};
const validatedLoginBody = loginSchemaOccupant.safeParse(body);
if (!validatedLoginBody.success) {

View File

@ -63,7 +63,6 @@ export async function handleCreateOperation(
}
if (createFunction) {
console.log("Body:", body);
const result = await createFunction(body);
return createResponse(result);
}
@ -90,7 +89,6 @@ export async function handleUpdateOperation(
return errorResponse("UUID not found", 400);
}
if (updateFunction) {
console.log("Body:", body);
const result = await updateFunction(body, uuid);
return updateResponse(result);
}
@ -136,12 +134,10 @@ export function createCreateHandler(
createFunction?: CreateFunction,
requiredFields: string[] = []
) {
console.log("Required fields:", requiredFields);
// This handler only takes the body parameter, not the request
return withErrorHandling((body: any) => {
// Ensure we're only passing the actual body data to the create function
if (body && typeof body === 'object' && body.body) {
console.log("Extracting body from request body");
return handleCreateOperation(body.body, createFunction, requiredFields);
}
return handleCreateOperation(body, createFunction, requiredFields);

View File

@ -2,10 +2,11 @@ import { ContentProps } from "@/validations/mutual/dashboard/props";
import { resolveWhichPageToRenderMulti } from "@/pages/resolver/resolver";
import ContentToRenderNoPage from "@/pages/mutual/noContent/page";
const PageToBeChildrendMulti: React.FC<ContentProps> = async ({ lang, translations, activePageUrl, mode }) => {
const PageToBeChildrendMulti: React.FC<ContentProps> = async ({ lang, activePageUrl, mode }) => {
const ApplicationToRender = await resolveWhichPageToRenderMulti({ activePageUrl })
if (!ApplicationToRender) return <ContentToRenderNoPage lang={lang} />
return <ApplicationToRender lang={lang} translations={translations} activePageUrl={activePageUrl} mode={mode} />
return <ApplicationToRender lang={lang} activePageUrl={activePageUrl} mode={mode} />
}
export default PageToBeChildrendMulti

View File

@ -2,18 +2,23 @@
import { FC, Suspense } from "react";
import { ContentProps, ModeTypes, ModeTypesList } from "@/validations/mutual/dashboard/props";
import LoadingContent from "@/components/mutual/loader/component";
import PageToBeChildrendSingle from "./PageToBeChildrendSingle";
import PageToBeChildrendMulti from "./PageToBeChildrendMulti";
const ContentComponent: FC<ContentProps> = async ({ lang, translations, activePageUrl, isMulti, mode }) => {
// const ContentComponent: FC<ContentProps> = async ({ lang, translations, activePageUrl, isMulti, mode }) => {
// const modeFromQuery = ModeTypesList.includes(mode || '') ? mode : 'shortList'
// const renderProps = { lang, translations, activePageUrl, mode: modeFromQuery as ModeTypes }
// const PageToBeChildrend = isMulti ? PageToBeChildrendMulti : PageToBeChildrendSingle
// const loadingContent = <LoadingContent height="h-16" size="w-36 h-48" plane="h-full w-full" />
// const classNameDiv = "fixed top-24 left-80 right-0 py-10 px-15 border-emerald-150 border-l-2 overflow-y-auto h-[calc(100vh-64px)]"
// return <div className={classNameDiv}><Suspense fallback={loadingContent}><PageToBeChildrend {...renderProps} /></Suspense></div>
// };
const ContentComponent: FC<ContentProps> = async ({ lang, activePageUrl, mode }) => {
const modeFromQuery = ModeTypesList.includes(mode || '') ? mode : 'shortList'
const renderProps = { lang, translations, activePageUrl, mode: modeFromQuery as ModeTypes }
const PageToBeChildrend = isMulti ? PageToBeChildrendMulti : PageToBeChildrendSingle
const renderProps = { lang, activePageUrl, mode: modeFromQuery as ModeTypes }
const loadingContent = <LoadingContent height="h-16" size="w-36 h-48" plane="h-full w-full" />
const classNameDiv = "fixed top-24 left-80 right-0 py-10 px-15 border-emerald-150 border-l-2 overflow-y-auto h-[calc(100vh-64px)]"
return (
<div className={classNameDiv}><Suspense fallback={loadingContent}><PageToBeChildrend {...renderProps} /></Suspense></div>
);
return <div className={classNameDiv}><Suspense fallback={loadingContent}><PageToBeChildrendMulti {...renderProps} /></Suspense></div>
};
export default ContentComponent;

View File

@ -1,12 +1,23 @@
'use server';
import { FC } from "react";
import { langGetKey } from "@/lib/langGet";
import { FooterProps } from "@/validations/mutual/dashboard/props";
import { AllProps } from "@/validations/mutual/dashboard/props";
const FooterComponent: FC<FooterProps> = ({ translations }) => {
const translations = {
en: {
footer: "footer",
page: "page"
},
tr: {
footer: "footer",
page: "sayfa"
}
}
const FooterComponent: FC<AllProps> = ({ lang, activePageUrl, prefix, mode }) => {
return (
<div className="fixed text-center bottom-0 left-0 right-0 h-16 p-4 border-t border-emerald-150 border-t-2 shadow-sm backdrop-blur-sm bg-emerald-50">
<h1>{langGetKey(translations, "footer")}: {langGetKey(translations, "page")}</h1>
<h1>{langGetKey(translations[lang], "footer")}: {langGetKey(translations[lang], "page")}</h1>
</div>
);
};

View File

@ -1,17 +1,35 @@
'use server';
import { FC } from "react";
import { HeaderProps } from "@/validations/mutual/dashboard/props";
'use client';
import { FC, useState, useEffect } from "react";
import { AllProps } from "@/validations/mutual/dashboard/props";
import LanguageSelectionComponent from "@/components/mutual/languageSelection/component";
import { langGetKey } from "@/lib/langGet";
import { checkContextPageOnline } from "@/components/mutual/context/online/context";
const HeaderComponent: FC<HeaderProps> = ({ translations, lang, activePageUrl, prefix }) => {
const translations = {
en: {
selectedPage: "selectedPage",
page: "page"
},
tr: {
selectedPage: "seçiliSayfa",
page: "sayfa"
}
}
const HeaderComponent: FC<AllProps> = ({ lang, activePageUrl, prefix, mode }) => {
const [online, setOnline] = useState(false);
useEffect(() => {
checkContextPageOnline().then((online) => {
setOnline(online);
});
}, []);
return (
<div className="flex justify-between h-24 items-center p-4 border-emerald-150 border-b-2 shadow-sm backdrop-blur-sm sticky top-0 z-50 bg-emerald-50">
<div className="flex flex-row justify-center items-center">
<p className="text-2xl font-bold mx-3">{langGetKey(translations, 'selectedPage')} :</p>
<p className="text-lg font-bold mx-3"> {langGetKey(translations, 'page')}</p>
<p className="text-2xl font-bold mx-3">{langGetKey(translations[lang], 'selectedPage')} :</p>
<p className="text-lg font-bold mx-3"> {langGetKey(translations[lang], 'page')}</p>
</div>
<div>{JSON.stringify(online)}</div>
<LanguageSelectionComponent lang={lang} activePage={activePageUrl} prefix={prefix} />
</div>
);

View File

@ -1,11 +1,12 @@
'use client';
import { FC, useState, useEffect } from "react";
import { MenuProps } from "@/validations/mutual/dashboard/props";
import { langGetKey } from "@/lib/langGet";
import { AllProps } from "@/validations/mutual/dashboard/props";
import { parseURlFormString } from "@/lib/menuGet";
import { menuTranslation } from "@/languages/mutual/menu"
import FirstLayerDropdown from "./firstLayerComponent";
import SecondLayerDropdown from "./secondLayerComponent";
import ThirdLayerDropdown from "./thirdLayerComponent";
import { checkContextPageMenu } from "@/components/mutual/context/menu/context";
// Define types for menu structure
type ThirdLayerItem = Record<string, any>;
@ -13,146 +14,109 @@ type SecondLayerItems = Record<string, ThirdLayerItem>;
type FirstLayerItems = Record<string, SecondLayerItems>;
type MenuStructure = FirstLayerItems;
const MenuComponent: FC<MenuProps> = ({ lang, menuItems, menuTranslationsFlatten, activePageUrl }) => {
// State for tracking expanded menu items
// Helper function to get translation for a URL path
const getMenuTranslation = (translations: any, urlPath: string) => {
if (translations[urlPath]) {
return translations[urlPath];
}
const cleanPath = urlPath.startsWith('/') ? urlPath.substring(1) : urlPath;
if (translations[cleanPath]) {
return translations[cleanPath];
}
const pathWithSlash = urlPath.startsWith('/') ? urlPath : `/${urlPath}`;
if (translations[pathWithSlash]) {
return translations[pathWithSlash];
}
const keys = Object.keys(translations);
for (const key of keys) {
const cleanKey = key.startsWith('/') ? key.substring(1) : key;
const cleanUrlPath = urlPath.startsWith('/') ? urlPath.substring(1) : urlPath;
if (cleanUrlPath.includes(cleanKey) || cleanKey.includes(cleanUrlPath)) {
return translations[key];
}
}
const parts = urlPath.split('/');
return parts[parts.length - 1] || urlPath;
};
const MenuComponent: FC<AllProps> = ({ lang, activePageUrl, prefix, mode }) => {
const [availableApplications, setAvailableApplications] = useState<string[]>([]);
useEffect(() => {
checkContextPageMenu().then((apps) => {
console.log('apps', apps);
setAvailableApplications(apps?.selectionList || []);
});
}, []);
const [expandedFirstLayer, setExpandedFirstLayer] = useState<string | null>(null);
const [expandedSecondLayer, setExpandedSecondLayer] = useState<string | null>(null);
const [menuStructure, setMenuStructure] = useState<MenuStructure>({});
// Parse active URL to determine which menu items should be active
const menuTranslationWLang = menuTranslation[lang];
const activePathLayers = parseURlFormString(activePageUrl).data;
const activeFirstLayer = activePathLayers[0] || null;
const activeSecondLayer = activePathLayers[1] || null;
const activeThirdLayer = activePathLayers[2] || null;
const activeThirdLayer = activePathLayers.slice(2, activePathLayers.length).join("/");
// Initialize expanded state based on active path
useEffect(() => {
if (activeFirstLayer) {
setExpandedFirstLayer(activeFirstLayer);
if (activeSecondLayer) {
setExpandedSecondLayer(activeSecondLayer);
const newMenuStructure: MenuStructure = {};
availableApplications.forEach((appPath: string) => {
const cleanPath = appPath.startsWith('/') ? appPath.substring(1) : appPath;
const pathParts = cleanPath.split('/');
if (pathParts.length >= 3) {
const firstLayer = pathParts[0];
const secondLayer = pathParts[1];
const thirdLayer = pathParts.slice(2).join('/');
if (!newMenuStructure[firstLayer]) { newMenuStructure[firstLayer] = {} }
if (!newMenuStructure[firstLayer][secondLayer]) { newMenuStructure[firstLayer][secondLayer] = {} }
newMenuStructure[firstLayer][secondLayer][thirdLayer] = true;
}
}
}, [activeFirstLayer, activeSecondLayer]);
// Process menu items into a hierarchical structure
useEffect(() => {
const processedStructure: MenuStructure = {};
Object.entries(menuItems).forEach(([path, _]: [string, any]) => {
const layers = parseURlFormString(path).data;
const firstLayer = layers[0];
const secondLayer = layers[1];
const thirdLayer = layers[2];
// Create first layer if it doesn't exist
if (!processedStructure[firstLayer]) {
processedStructure[firstLayer] = {};
}
// Create second layer if it doesn't exist
if (!processedStructure[firstLayer][secondLayer]) {
processedStructure[firstLayer][secondLayer] = {};
}
// Add third layer
processedStructure[firstLayer][secondLayer][thirdLayer] = {};
});
setMenuStructure(newMenuStructure);
}, [availableApplications]);
setMenuStructure(processedStructure);
}, [menuItems]);
useEffect(() => { if (activeFirstLayer) { setExpandedFirstLayer(activeFirstLayer); if (activeSecondLayer) { setExpandedSecondLayer(activeSecondLayer) } } }, [activeFirstLayer, activeSecondLayer]);
// Handle click on first layer menu item
const handleFirstLayerClick = (key: string) => {
if (expandedFirstLayer === key) {
// If already expanded, collapse it
setExpandedFirstLayer(null);
setExpandedSecondLayer(null);
} else {
// Otherwise expand it
setExpandedFirstLayer(key);
setExpandedSecondLayer(null);
}
};
const handleFirstLayerClick = (key: string) => { if (expandedFirstLayer === key) { setExpandedFirstLayer(null); setExpandedSecondLayer(null) } else { setExpandedFirstLayer(key); setExpandedSecondLayer(null) } };
const handleSecondLayerClick = (key: string) => { if (expandedSecondLayer === key) { setExpandedSecondLayer(null) } else { setExpandedSecondLayer(key) } };
// Handle click on second layer menu item
const handleSecondLayerClick = (key: string) => {
if (expandedSecondLayer === key) {
// If already expanded, collapse it
setExpandedSecondLayer(null);
} else {
// Otherwise expand it
setExpandedSecondLayer(key);
}
};
// Render third layer menu items
const renderThirdLayerItems = (firstLayerKey: string, secondLayerKey: string, thirdLayerItems: ThirdLayerItem) => {
const baseUrl = `/${firstLayerKey}/${secondLayerKey}`;
return Object.keys(thirdLayerItems).map(thirdLayerKey => {
const isActive =
activeFirstLayer === firstLayerKey &&
activeSecondLayer === secondLayerKey &&
activeThirdLayer === thirdLayerKey;
const isActive = activeFirstLayer === firstLayerKey && activeSecondLayer === secondLayerKey && activeThirdLayer === thirdLayerKey;
const mergeUrl = `${baseUrl}/${thirdLayerKey}`;
const url = `/${lang}${baseUrl}/${thirdLayerKey}`;
return (
<div key={`${thirdLayerKey}-item`} className="ml-2 my-1">
<ThirdLayerDropdown
isActive={isActive}
innerText={langGetKey(menuTranslationsFlatten, thirdLayerKey)}
url={url}
/>
</div>
);
console.log('mergeUrl', mergeUrl);
return <div key={`${thirdLayerKey}-item`} className="ml-2 my-1"><ThirdLayerDropdown isActive={isActive} innerText={getMenuTranslation(menuTranslationWLang, mergeUrl)} url={url} /></div>
});
};
// Render second layer menu items
const renderSecondLayerItems = (firstLayerKey: string, secondLayerItems: SecondLayerItems) => {
return Object.entries(secondLayerItems).map(([secondLayerKey, thirdLayerItems]) => {
const isActive = activeFirstLayer === firstLayerKey && activeSecondLayer === secondLayerKey;
const isExpanded = expandedSecondLayer === secondLayerKey;
const mergeUrl = `/${firstLayerKey}/${secondLayerKey}`;
console.log('mergeUrl', mergeUrl);
return (
<div key={`${secondLayerKey}-item`} className="ml-2 my-1">
<SecondLayerDropdown
isActive={isActive}
isExpanded={isExpanded}
innerText={langGetKey(menuTranslationsFlatten, secondLayerKey)}
onClick={() => handleSecondLayerClick(secondLayerKey)}
/>
{isExpanded && (
<div className="ml-2 mt-1">
{renderThirdLayerItems(firstLayerKey, secondLayerKey, thirdLayerItems)}
</div>
)}
<SecondLayerDropdown isActive={isActive} isExpanded={isExpanded} innerText={getMenuTranslation(menuTranslationWLang, mergeUrl)} onClick={() => handleSecondLayerClick(secondLayerKey)} />
{isExpanded && (<div className="ml-2 mt-1">{renderThirdLayerItems(firstLayerKey, secondLayerKey, thirdLayerItems)}</div>)}
</div>
);
});
};
// Render first layer menu items
const renderFirstLayerItems = () => {
return Object.entries(menuStructure).map(([firstLayerKey, secondLayerItems]) => {
const isActive = activeFirstLayer === firstLayerKey;
const isExpanded = expandedFirstLayer === firstLayerKey;
const mergeUrl = `/${firstLayerKey}`;
console.log('mergeUrl', mergeUrl);
return (
<div key={`${firstLayerKey}-item`} className="mb-2">
<FirstLayerDropdown
isActive={isActive}
isExpanded={isExpanded}
innerText={langGetKey(menuTranslationsFlatten, firstLayerKey)}
onClick={() => handleFirstLayerClick(firstLayerKey)}
/>
{isExpanded && (
<div className="mt-1">
{renderSecondLayerItems(firstLayerKey, secondLayerItems)}
</div>
)}
<FirstLayerDropdown isActive={isActive} isExpanded={isExpanded} innerText={getMenuTranslation(menuTranslationWLang, mergeUrl)} onClick={() => handleFirstLayerClick(firstLayerKey)} />
{isExpanded && (<div className="mt-1">{renderSecondLayerItems(firstLayerKey, secondLayerItems)}</div>)}
</div>
);
});

View File

@ -0,0 +1,35 @@
import { ClientPageConfig } from "@/types/mutual/context/validations";
import { API_BASE_URL } from "@/config/config";
async function checkContextPageConfig(): Promise<ClientPageConfig> {
const result = await fetch(`${API_BASE_URL}/context/page/config`, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
try {
const data = await result.json();
return data;
} catch (error) {
throw new Error("No data is found");
}
}
async function setContextPageConfig({
pageConfig,
}: {
pageConfig: ClientPageConfig;
}) {
const result = await fetch(`${API_BASE_URL}/context/page/config`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(pageConfig),
});
try {
const data = await result.json();
return data;
} catch (error) {
throw new Error("No data is set");
}
}
export { checkContextPageConfig, setContextPageConfig };

View File

@ -0,0 +1,34 @@
import { ClientMenu } from "@/types/mutual/context/validations";
import { API_BASE_URL } from "@/config/config";
async function checkContextPageMenu() {
const result = await fetch(`${API_BASE_URL}/context/page/menu`, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
try {
const data = await result.json();
console.log("checkContextPageMenu ", data);
if (data.status == 200) return data.data;
} catch (error) {
console.log(error);
throw new Error("No data is found");
}
}
async function setContextPageMenu(setMenu: ClientMenu) {
const result = await fetch(`${API_BASE_URL}/context/page/menu`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(setMenu),
});
try {
const data = await result.json();
return data;
} catch (error) {
console.log(error);
throw new Error("No data is set");
}
}
export { checkContextPageMenu, setContextPageMenu };

View File

@ -0,0 +1,29 @@
import { ClientOnline } from "@/types/mutual/context/validations";
import { API_BASE_URL } from "@/config/config";
async function checkContextPageOnline() {
const result = await fetch(`${API_BASE_URL}/context/page/online`, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
try {
const data = await result.json();
if (data.status == 200) return data.data;
} catch (error) {}
return null;
}
async function setContextPageOnline(setOnline: ClientOnline) {
const result = await fetch(`${API_BASE_URL}/context/page/online`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(setOnline),
});
try {
const data = await result.json();
if (data.status == 200) return data.data;
} catch (error) {}
return null;
}
export { checkContextPageOnline, setContextPageOnline };

View File

@ -0,0 +1,37 @@
import { ClientSelection } from "@/types/mutual/context/validations";
import { API_BASE_URL } from "@/config/config";
async function checkContextDashSelection() {
try {
const result = await fetch(`${API_BASE_URL}/context/dash/selection`, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
const data = await result.json();
if (data.status === 200) return data.data;
} catch (error) {}
return null;
}
async function setContextDashUserSelection({
userSet,
}: {
userSet: ClientSelection;
}) {
try {
const result = await fetch(`${API_BASE_URL}/context/dash/selection`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userSet),
});
const data = await result.json();
if (data.status === 200) return data.data;
} catch (error) {
console.error("Error setting dash selection:", error);
}
return null;
}
export { checkContextDashSelection, setContextDashUserSelection };

View File

@ -0,0 +1,33 @@
import { ClientUser } from "@/types/mutual/context/validations";
import { API_BASE_URL } from "@/config/config";
async function checkContextDashUserInfo(): Promise<ClientUser> {
const result = await fetch(`${API_BASE_URL}/context/dash/user`, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
try {
const data = await result.json();
return data;
} catch (error) {
console.log(error);
throw new Error("No data is found");
}
}
async function setContextDashUserInfo({ userSet }: { userSet: ClientUser }) {
const result = await fetch(`${API_BASE_URL}/context/dash/user`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userSet),
});
try {
const data = await result.json();
return data;
} catch (error) {
console.log(error);
throw new Error("No data is set");
}
}
export { checkContextDashUserInfo, setContextDashUserInfo };

View File

@ -1,31 +1,25 @@
'use server';
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent } from "@/components/mutual/shadcnui/dropdown-menu";
'use client';
import { useState, useEffect } from "react";
import { DropdownMenu, DropdownMenuTrigger } from "@/components/mutual/shadcnui/dropdown-menu";
import { Button } from "@/components/mutual/shadcnui/button";
import { languageSelectionTranslation } from "@/languages/mutual/languageSelection";
import { langGetKey, langGet } from "@/lib/langGet";
import { LanguageTypes } from "@/validations/mutual/language/validations";
import { checkContextPageOnline, setContextPageOnline } from "@/components/mutual/context/online/context";
import LanguageSelectionItem from "./languageItem";
const LanguageSelectionComponent: React.FC<{ lang: LanguageTypes, activePage: string, prefix: string }> = ({ lang, activePage, prefix }) => {
const translations = langGet(lang, languageSelectionTranslation);
const getPageWithLocale = (locale: LanguageTypes): string => { return `${prefix}/${locale}/${activePage}` }
const englishButtonProps = {
activeLang: lang,
buttonsLang: "en",
refUrl: getPageWithLocale("en"),
innerText: langGetKey(translations, "english")
}
const turkishButtonProps = {
activeLang: lang,
buttonsLang: "tr",
refUrl: getPageWithLocale("tr"),
innerText: langGetKey(translations, "turkish")
}
const getPageWithLocale = (locale: LanguageTypes): string => { return `${prefix}/${activePage}` }
const [online, setOnline] = useState<any>({});
useEffect(() => { const online = checkContextPageOnline(); setOnline({ ...online, lang: lang }) }, []);
const englishButtonProps = { activeLang: lang, buttonsLang: "en", refUrl: getPageWithLocale("en"), innerText: langGetKey(translations, "english") }
const turkishButtonProps = { activeLang: lang, buttonsLang: "tr", refUrl: getPageWithLocale("tr"), innerText: langGetKey(translations, "turkish") }
return (
<div className="flex items-end justify-end">
<div>{JSON.stringify(online)}</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="w-48 h-12 text-center text-md">{langGetKey(translations, "title")}</Button>

View File

@ -3,10 +3,21 @@ import { useState, FC } from "react";
import { DropdownMenuContent, DropdownMenuLabel } from "@/components/mutual/shadcnui/dropdown-menu";
import Link from "next/link";
import LoadingContent from "@/components/mutual/loader/component";
import { setContextPageOnline, checkContextPageOnline } from "@/components/mutual/context/online/context";
const RenderLinkComponent: FC<{ refUrl: string, innerText: string, setisL: (isLoading: boolean) => void }> = ({ refUrl, innerText, setisL }) => {
const RenderLinkComponent: FC<{ refUrl: string, innerText: string, setisL: (isLoading: boolean) => void, buttonsLang: string }> = (
{ refUrl, innerText, setisL, buttonsLang }) => {
const setOnline = async () => {
setisL(true);
const oldOnline = await checkContextPageOnline();
await setContextPageOnline({
...oldOnline,
lang: buttonsLang,
});
setisL(false);
}
return (
<Link replace href={refUrl} onClick={() => setisL(true)}>
<Link replace href={refUrl} onClick={() => { setOnline() }}>
<DropdownMenuContent className="flex w-48 h-12 align-center justify-center text-center text-md overflow-y-hidden">
<DropdownMenuLabel className="flex items-center justify-center">{innerText}</DropdownMenuLabel>
</DropdownMenuContent>
@ -29,7 +40,7 @@ const LanguageSelectionItem: React.FC<{
}> = ({ activeLang, buttonsLang, refUrl, innerText }) => {
const [isL, setisL] = useState<boolean>(false);
const isC = buttonsLang !== activeLang
const RenderLinkProp = { refUrl, innerText, setisL }
const RenderLinkProp = { refUrl, innerText, setisL, buttonsLang }
return (
<>{isC && <>{isL ? <RenderLoadingComponent setisL={setisL} /> : <RenderLinkComponent {...RenderLinkProp} />}</>}</>
)

View File

@ -1,14 +1,24 @@
import { LanguageTypes } from "@/validations/mutual/language/validations";
import { DynamicPage } from "@/validations/mutual/menu/menu";
import { managementAccountTenantMain } from "./management/account/tenantSomething/index";
import { managementAccountTenantMainSecond } from "./management/account/tenantSomethingSecond/index";
import { buildingPartsTenantSomething } from "./building/parts/tenantSomething/index";
// import { managementAccountTenantMainSecond } from "./management/account/tenantSomethingSecond/index";
// import { buildingPartsTenantSomething } from "./building/parts/tenantSomething/index";
const dynamicPagesIndex: Record<string, Record<LanguageTypes, DynamicPage>> = {
"main/pages/user/dashboard": managementAccountTenantMain,
"management/account/tenant/something": managementAccountTenantMain,
"management/account/tenant/somethingSecond": managementAccountTenantMainSecond,
"building/parts/tenant/something": buildingPartsTenantSomething,
"/main/pages/user/dashboard": managementAccountTenantMain,
"/definitions/identifications/people": managementAccountTenantMain,
"/definitions/identifications/users": managementAccountTenantMain,
"/definitions/building/parts": managementAccountTenantMain,
"/definitions/building/areas": managementAccountTenantMain,
"/building/accounts/managment/accounts": managementAccountTenantMain,
"/building/accounts/managment/budgets": managementAccountTenantMain,
"/building/accounts/parts/accounts": managementAccountTenantMain,
"/building/accounts/parts/budgets": managementAccountTenantMain,
"/building/meetings/regular/actions": managementAccountTenantMain,
"/building/meetings/regular/accounts": managementAccountTenantMain,
"/building/meetings/ergunt/actions": managementAccountTenantMain,
"/building/meetings/ergunt/accounts": managementAccountTenantMain,
"/building/meetings/invited/attendance": managementAccountTenantMain,
};
export { dynamicPagesIndex };

View File

@ -1,11 +1,27 @@
const menuTranslationEn = {
"/management/account/something/something/something": "Account Third Layer",
"/management/account": "Account Second Layer",
"/management": "Management First Layer",
};
const menuIndex = [
"/management/account/something/something/something",
"/building/parts/something/something/something",
];
"/definitions": "Definitions",
"/definitions/identifications": "Identifications",
"/definitions/identifications/people": "People",
"/definitions/identifications/users": "Users",
export { menuTranslationEn, menuIndex };
"/definitions/building": "Building",
"/definitions/building/parts": "Build Parts",
"/definitions/building/areas": "Building Areas",
"/building": "Building",
"/building/accounts": "Account Actions",
"/building/accounts/managment/accounts": "Management Accounts",
"/building/accounts/managment/budgets": "Management Budgets",
"/building/accounts/parts/accounts": "Parts Accounts",
"/building/accounts/parts/budgets": "Parts Budgets",
"/building/meetings": "Meetings",
"/building/meetings/regular/actions": "Regular Meeting Actions",
"/building/meetings/regular/accounts": "Regular Meeting Accounts",
"/building/meetings/ergunt/actions": "Ergunt Meeting Actions",
"/building/meetings/ergunt/accounts": "Ergunt Meeting Accounts",
"/building/meetings/invited/attendance": "Meeting Invited Attendance",
};
export { menuTranslationEn };
// export { menuTranslationEn, menuIndex };

View File

@ -1,3 +1,26 @@
const menuTranslationTr = {};
const menuTranslationTr = {
"/definitions": "Tanımlamalar",
"/definitions/identifications": "Tanımlamalar",
"/definitions/identifications/people": "Kişiler",
"/definitions/identifications/users": "Kullanıcılar",
"/definitions/building": "Bina",
"/definitions/building/parts": "Daireler",
"/definitions/building/areas": "Bina Alanları",
"/building": "Bina",
"/building/accounts": "Account Eylemleri",
"/building/accounts/managment/accounts": "Bina Hesapları",
"/building/accounts/managment/budgets": "Bina Bütçesi",
"/building/accounts/parts/accounts": "Daire Hesapları",
"/building/accounts/parts/budgets": "Daire Bütçesi",
"/building/meetings": "Toplantılar",
"/building/meetings/regular/actions": "Düzenli Toplantı Eylemleri",
"/building/meetings/regular/accounts": "Düzenli Toplantı Accounts",
"/building/meetings/ergunt/actions": "Ergunt Toplantı Eylemleri",
"/building/meetings/ergunt/accounts": "Ergunt Toplantı Accounts",
"/building/meetings/invited/attendance": "Toplantı Davetli Katılımlar",
};
export { menuTranslationTr };

View File

@ -1,8 +1,5 @@
'use server';
import { FC } from "react";
import { joinPageUrlFromLayersArray, retrieveLayersOfUrlFromParams } from "@/lib/menuGet";
import { dynamicPagesIndex } from "@/languages/custom";
import { dynamicPageMenuWithLayersGet, dynamicRetrieveMenuFlattenGet, langDynamicPagesGet, langGet } from "@/lib/langGet";
import { DashboardLayoutProps, ModeTypes } from "@/validations/mutual/dashboard/props";
import HeaderComponent from "@/components/custom/header/component";
@ -11,26 +8,18 @@ import ContentComponent from "@/components/custom/content/component";
import FooterComponent from "@/components/custom/footer/component";
const DashboardLayout: FC<DashboardLayoutProps> = async ({ params, searchParams, lang }) => {
const layersItems = retrieveLayersOfUrlFromParams(params.page);
const activePageUrl = joinPageUrlFromLayersArray(layersItems.data);
const mode = (searchParams?.mode as ModeTypes) || 'shortList';
const menuItems = await dynamicPageMenuWithLayersGet(lang);
const translations = langGet(lang, langDynamicPagesGet(activePageUrl, dynamicPagesIndex));
const menuTranslationsFlatten = dynamicRetrieveMenuFlattenGet(menuItems);
const headerProps = { translations: translations.header, lang, activePageUrl, prefix: "/panel" }
const menuProps = { lang, activePageUrl, menuTranslationsFlatten, menuItems }
const contentProps = { translations: translations.content, lang, activePageUrl, mode, isMulti: true }
const activePageUrl = `/${params.page?.join('/')}`;
const allProps = { lang, activePageUrl, mode, prefix: "/panel" }
return (
<div className="flex flex-col min-w-screen">
<HeaderComponent {...headerProps} />
<MenuComponent {...menuProps} />
<ContentComponent {...contentProps} />
<FooterComponent translations={translations.footer} />
<HeaderComponent {...allProps} />
<MenuComponent {...allProps} />
<ContentComponent {...allProps} />
<FooterComponent {...allProps} />
</div>
);
}
export { DashboardLayout };
export { DashboardLayout };

View File

@ -1,21 +1,21 @@
import { ContentProps } from "@/validations/mutual/dashboard/props";
import superUserTenantSomething from "./management/account/tenantSomething/page";
import superUserTenantSomethingSecond from "./management/account/tenantSomethingSecond/page";
import superUserBuildingPartsTenantSomething from "./building/parts/tenantSomething/page";
const pageIndexMulti: Record<string, Record<string, React.FC<ContentProps>>> = {
"main/pages/user/dashboard": {
superUserTenantSomething: superUserTenantSomething,
},
"management/account/tenant/something": {
superUserTenantSomething: superUserTenantSomething,
},
"management/account/tenant/somethingSecond": {
superUserTenantSomething: superUserTenantSomethingSecond,
},
"building/parts/tenant/something": {
superUserTenantSomething: superUserBuildingPartsTenantSomething,
},
"/main/pages/user/dashboard": { superUserTenantSomething },
"/definitions/identifications/people": { superUserTenantSomething },
"/definitions/identifications/users": { superUserTenantSomething },
"/definitions/building/parts": { superUserTenantSomething },
"/definitions/building/areas": { superUserTenantSomething },
"/building/accounts/managment/accounts": { superUserTenantSomething },
"/building/accounts/managment/budgets": { superUserTenantSomething },
"/building/accounts/parts/accounts": { superUserTenantSomething },
"/building/accounts/parts/budgets": { superUserTenantSomething },
"/building/meetings/regular/actions": { superUserTenantSomething },
"/building/meetings/regular/accounts": { superUserTenantSomething },
"/building/meetings/ergunt/actions": { superUserTenantSomething },
"/building/meetings/ergunt/accounts": { superUserTenantSomething },
"/building/meetings/invited/attendance": { superUserTenantSomething },
};
export default pageIndexMulti;

View File

@ -0,0 +1,187 @@
// From Redis objects
interface ClientOnline {
lastLogin: Date;
lastLogout: Date;
lastAction: Date;
lastPage: string;
userType: string;
lang: string;
timezone: string;
}
interface ClientPageConfig {
mode: string;
textFont: number;
theme: string;
}
interface ClientHeader {
header: any[];
activeDomain: string;
listOfDomains: string[];
connections: any[];
}
interface ClientMenu {
selectionList: string[];
activeSelection: string;
}
interface ClientSelection {
selectionList: any[];
activeSelection: Record<string, any>;
}
interface ClientUser {
uuid: string;
avatar: string;
email: string;
phone_number: string;
user_tag: string;
password_expiry_begins: string;
person: {
uuid: string;
firstname: string;
surname: string;
middle_name: string;
sex_code: string;
person_tag: string;
country_code: string;
birth_date: string;
};
}
interface ClientSettings {
lastOnline: Date;
token: string;
}
interface LinkList {
linkList: any[];
}
interface ClientRedisToken {
online: ClientOnline;
pageConfig: ClientPageConfig;
menu: ClientMenu;
header: ClientHeader;
selection: ClientSelection;
user: ClientUser;
settings: ClientSettings;
chatRoom: LinkList;
notifications: LinkList;
messages: LinkList;
}
const defaultClientOnline: ClientOnline = {
lastLogin: new Date(),
lastLogout: new Date(),
lastAction: new Date(),
lastPage: "/dashboard",
userType: "employee",
lang: "tr",
timezone: "GMT+3",
};
const defaultClientPageConfig: ClientPageConfig = {
mode: "light",
textFont: 14,
theme: "default",
};
const defaultClientMenu: ClientMenu = {
selectionList: [],
activeSelection: "/dashboard",
};
const defaultClientHeader: ClientHeader = {
header: [],
activeDomain: "",
listOfDomains: [],
connections: [],
};
const defaultClientSelection: ClientSelection = {
selectionList: [],
activeSelection: {},
};
const defaultClientUser: ClientUser = {
uuid: "",
avatar: "",
email: "",
phone_number: "",
user_tag: "",
password_expiry_begins: new Date().toISOString(),
person: {
uuid: "",
firstname: "",
surname: "",
middle_name: "",
sex_code: "",
person_tag: "",
country_code: "",
birth_date: "",
},
};
const defaultClientSettings: ClientSettings = {
lastOnline: new Date(),
token: "",
};
const defaultLinkList: LinkList = {
linkList: [],
};
const defaultClientRedisToken: ClientRedisToken = {
online: defaultClientOnline,
pageConfig: defaultClientPageConfig,
menu: defaultClientMenu,
header: defaultClientHeader,
selection: defaultClientSelection,
user: defaultClientUser,
settings: defaultClientSettings,
chatRoom: defaultLinkList,
notifications: defaultLinkList,
messages: defaultLinkList,
};
const generateRedisKey = (userType: string, uuId: string) => {
const userTypeToUpper = userType.toUpperCase();
if (!userTypeToUpper || !uuId) throw new Error("Invalid user type or uuId");
return `CLIENT:${userTypeToUpper}:${uuId}`;
};
const readRedisKey = (redisKey: string) => {
if (redisKey.split(":").length !== 2) throw new Error("Invalid redis key");
const userTypeToUpper = redisKey.split(":")[1];
const uuId = redisKey.split(":")[2];
if (!userTypeToUpper || !uuId) throw new Error("Invalid user type or uuId");
return { userType: userTypeToUpper.toLowerCase(), uuId };
};
export {
defaultClientOnline,
defaultClientPageConfig,
defaultClientMenu,
defaultClientHeader,
defaultClientSelection,
defaultClientUser,
defaultClientSettings,
defaultLinkList,
defaultClientRedisToken,
};
export type {
ClientOnline,
ClientPageConfig,
ClientMenu,
ClientHeader,
ClientSelection,
ClientUser,
ClientSettings,
LinkList,
ClientRedisToken,
};
export { generateRedisKey, readRedisKey };

View File

@ -0,0 +1,44 @@
interface FetcherRequest {
url: string;
isNoCache: boolean;
}
interface PostFetcherRequest<T> extends FetcherRequest {
body: Record<string, T>;
}
interface GetFetcherRequest extends FetcherRequest {
url: string;
}
interface DeleteFetcherRequest extends GetFetcherRequest {}
interface PutFetcherRequest<T> extends PostFetcherRequest<T> {}
interface PatchFetcherRequest<T> extends PostFetcherRequest<T> {}
interface FetcherRespose {
success: boolean;
}
interface PaginationResponse {
onPage: number;
onPageCount: number;
totalPage: number;
totalCount: number;
next: boolean;
back: boolean;
}
interface FetcherDataResponse<T> extends FetcherRespose {
data: Record<string, T> | null;
pagination?: PaginationResponse;
}
export type {
FetcherRequest,
PostFetcherRequest,
GetFetcherRequest,
DeleteFetcherRequest,
PutFetcherRequest,
PatchFetcherRequest,
FetcherRespose,
FetcherDataResponse,
};

View File

@ -17,28 +17,24 @@ const ModeTypesList = ["shortList", "fullList", "create", "update", "view"];
interface ContentProps {
lang: LanguageTypes;
translations: Record<string, string>;
activePageUrl: string;
isMulti?: boolean;
mode?: ModeTypes;
}
interface MenuProps {
lang: LanguageTypes;
menuItems: Record<string, Record<string, any>>;
availableApplications: string[];
activePageUrl: string;
menuTranslationsFlatten: Record<string, Record<string, any>>;
}
interface FooterProps {
translations: Record<string, string>;
}
interface FooterProps {}
interface HeaderProps {
translations: Record<string, string>;
interface AllProps {
lang: LanguageTypes;
activePageUrl: string;
prefix: string;
mode?: ModeTypes;
}
export type {
@ -47,7 +43,7 @@ export type {
ContentProps,
MenuProps,
FooterProps,
HeaderProps,
AllProps,
ModeTypes,
};

View File

@ -19,14 +19,12 @@ export function loginHook(
.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);
const rediretUrl = `/auth/select?type=${userType}`;
Router.push(rediretUrl);
}, 100);
});

View File

@ -1,30 +1,57 @@
"use client";
import React, { useTransition, useState } from "react";
import React, { useTransition, useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Company } from "./types";
import { LoginEmployeeProps } from "./types";
import { selectEmployeeHook } from "./hook";
import { checkContextDashSelection } from "@/components/mutual/context/selection/context";
import { ClientSelection } from "@/types/mutual/context/validations";
const translation = {
en: {
companySelection: "Company Selection",
loggedInAs: "Logged in as",
duty: "Duty",
id: "ID",
noSelections: "No selections",
continue: "Continue",
select: "Select"
},
tr: {
companySelection: "Şirket Seçimi",
loggedInAs: "Giriş Yapan",
duty: "Görev",
id: "ID",
noSelections: "Seçim Yok",
continue: "Devam Et",
select: "Seç"
}
}
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 [selectionListCopy, setSelectionListCopy] = useState<ClientSelection>({ selectionList: [], activeSelection: {} });
useEffect(() => {
checkContextDashSelection().then((selectionList) => {
if (!selectionList) throw new Error("No selection list found");
setSelectionListCopy(selectionList)
})
}, [])
const isArrayLengthOne = Array.isArray(selectionListCopy.selectionList) && selectionListCopy.selectionList.length === 1;
const isArrayLengthZero = Array.isArray(selectionListCopy.selectionList) && selectionListCopy.selectionList.length === 0;
const isArrayMoreThanOne = Array.isArray(selectionListCopy.selectionList) && selectionListCopy.selectionList.length > 1;
const Router = useRouter();
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const [jsonText, setJsonText] = useState<string | null>(null);
const onSubmitEmployee = async (uu_id: string) => {
selectEmployeeHook(startTransition, { company_uu_id: uu_id }, setError, setJsonText, Router, lang)
};
const onSubmitEmployee = async (uu_id: string) => { selectEmployeeHook(startTransition, { uuid: uu_id }, setError, setJsonText, Router, lang) };
// Render a company card with consistent styling
const CompanyCard = ({ company, showButton = false }: { company: Company, showButton?: boolean }) => (
const CompanyCard = ({ company }: { company: Company }) => (
<div className="w-full p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition-all">
<div className="flex flex-col" onClick={() => onSubmitEmployee(company.uu_id)}>
{/* Company name and type */}
@ -40,13 +67,13 @@ function LoginEmployee({
{/* Duty information */}
{company.duty && (
<div className="mb-2 text-sm text-gray-600">
<span className="font-medium">{translation.duty}:</span> {company.duty}
<span className="font-medium">{translation[lang].duty}:</span> {company.duty}
</div>
)}
{/* ID information */}
<div className="text-xs text-gray-500 mb-3">
<span className="font-medium">{translation.id}:</span> {company.uu_id}
<span className="font-medium">{translation[lang].id}:</span> {company.uu_id}
</div>
</div>
@ -55,30 +82,26 @@ function LoginEmployee({
return (
<div className="w-full max-w-md mx-auto">
<h1 className="text-2xl font-bold text-gray-900 mb-2">{translation.companySelection}</h1>
<p className="text-sm text-gray-500 mb-6">{translation.loggedInAs}</p>
<h1 className="text-2xl font-bold text-gray-900 mb-2">{translation[lang].companySelection}</h1>
<p className="text-sm text-gray-500 mb-6">{translation[lang].loggedInAs}</p>
{/* No companies available */}
{isArrayLengthZero && (
<div className="text-center p-6 bg-gray-50 rounded-lg border border-gray-200">
<p className="text-gray-600">{translation.noSelections}</p>
<p className="text-gray-600">{translation[lang].noSelections}</p>
</div>
)}
{/* Single company */}
{isArrayLengthOne && <CompanyCard company={selectionList[0]} showButton={true} />}
{isArrayLengthOne && <CompanyCard company={selectionListCopy.activeSelection as Company} />}
{/* Multiple companies */}
{isArrayMoreThanOne && (
<div className="space-y-3">
{selectionList.map((company, index) => (
<div
key={company.uu_id || index}
onClick={() => onSubmitEmployee(company.uu_id)}
{selectionListCopy.selectionList.map((company, index) => (
<div key={company.uu_id || index} onClick={() => onSubmitEmployee(company.uu_id)}
className="cursor-pointer hover:translate-x-1 transition-transform"
>
<CompanyCard company={company} />
</div>
><CompanyCard company={company as Company} /></div>
))}
</div>
)}

View File

@ -3,24 +3,38 @@ import React, { useState, useTransition, useEffect } from "react";
import { useRouter } from "next/navigation";
import { LoginOccupantProps } from "./types";
import { selectOccupantHook } from "./hook";
import { checkContextDashSelection } from "@/components/mutual/context/selection/context";
import { ClientSelection } from "@/types/mutual/context/validations";
function LoginOccupant({
selectionList,
translation,
lang
}: LoginOccupantProps) {
const translation = {
en: {
occupantSelection: "Occupant Selection",
loggedInAs: "Logged in as",
level: "Level",
noSelections: "No selections"
},
tr: {
occupantSelection: "İşçi Seçimi",
loggedInAs: "Giriş Yapan",
level: "Seviye",
noSelections: "Seçim Yok"
}
}
function LoginOccupant({ lang }: LoginOccupantProps) {
const Router = useRouter();
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const [jsonText, setJsonText] = useState<string | null>(null);
const [selectionList, setSelectionList] = useState<ClientSelection>({ selectionList: [], activeSelection: {} });
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
useEffect(() => {
checkContextDashSelection().then((selectionList) => {
if (!selectionList) throw new Error("No selection list found"); setSelectionList(selectionList);
})
}, [])
const onSubmitOccupant = async (data: any) => { selectOccupantHook(startTransition, data, setError, setJsonText, Router, lang) };
const isArrayLengthZero = Array.isArray(selectionList.selectionList) && selectionList.selectionList.length === 0;
const OccupantCard = ({ occupant, buildKey, idx }: { occupant: any, buildKey: string, idx: number }) => (
<div
key={`${buildKey}-${idx}`}
@ -28,28 +42,21 @@ function LoginOccupant({
onClick={() => onSubmitOccupant({ build_living_space_uu_id: occupant.build_living_space_uu_id })}
>
<div className="flex flex-col">
{/* Occupant description and code */}
<div className="flex items-center justify-between mb-2">
<h3 className="text-lg font-semibold text-gray-800">{occupant.description}</h3>
<span className="px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 rounded-full">
{occupant.code}
</span>
</div>
{/* Part name */}
<div className="mb-1 text-sm text-gray-700 font-medium">
{occupant.part_name}
</div>
{/* Level information */}
<div className="text-xs text-gray-500">
<span className="font-medium">{translation.level}:</span> {occupant.part_level}
<span className="font-medium">{translation[lang].level}:</span> {occupant.part_level}
</div>
</div>
</div>
);
// Render a building section with its occupants
const BuildingSection = ({ building, buildKey }: { building: any, buildKey: string }) => (
<div key={buildKey} className="mb-6">
<div className="p-3 bg-gray-50 border border-gray-200 rounded-lg mb-3">
@ -72,31 +79,25 @@ function LoginOccupant({
return (
<div className="w-full max-w-md mx-auto">
<h1 className="text-2xl font-bold text-gray-900 mb-2">{translation.occupantSelection}</h1>
<p className="text-sm text-gray-500 mb-6">{translation.loggedInAs}</p>
<h1 className="text-2xl font-bold text-gray-900 mb-2">{translation[lang].occupantSelection}</h1>
<p className="text-sm text-gray-500 mb-6">{translation[lang].loggedInAs}</p>
{/* No occupants available */}
{!isArrayLengthZero ? (
<div className="text-center p-6 bg-gray-50 rounded-lg border border-gray-200">
<p className="text-gray-600">{translation.noSelections}</p>
<p className="text-gray-600">{translation[lang].noSelections}</p>
</div>
) : (
/* Building sections with occupants */
<div>
{Object.keys(selectionList).map((buildKey: string) => (
{Object.keys(selectionList.selectionList).map((buildKey: string) => (
<BuildingSection
key={buildKey}
building={selectionList[buildKey]}
building={selectionList.activeSelection}
buildKey={buildKey}
/>
))}
</div>
)}
{/* Show error if any */}
{error && <p className="mt-4 text-sm text-red-600">{error}</p>}
{/* Loading indicator */}
{isPending && (
<div className="mt-4 flex justify-center">
<div className="animate-spin h-5 w-5 border-2 border-blue-600 rounded-full border-t-transparent"></div>

View File

@ -1,7 +1,7 @@
import { LanguageTypes } from "@/validations/mutual/language/validations";
const afterLoginDirectUrl = (lang: LanguageTypes) => {
return `/panel/${lang}/main/pages/user/dashboard`;
const afterLoginDirectUrl = () => {
return `/panel/dashboard`;
};
function selectEmployeeHook(
@ -25,7 +25,7 @@ function selectEmployeeHook(
response.json().then((data) => {
console.log("data", data);
setTimeout(() => {
Router.push(afterLoginDirectUrl(lang));
Router.push(afterLoginDirectUrl());
}, 100);
});
} else {
@ -62,7 +62,7 @@ function selectOccupantHook(
response.json().then((data) => {
console.log("data", data);
setTimeout(() => {
Router.push(afterLoginDirectUrl(lang));
Router.push(afterLoginDirectUrl());
}, 100);
});
} else {

View File

@ -1,39 +1,18 @@
"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<SelectListProps> = ({ 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<Company[]>(selectionList as Company[]);
const [listOccupantSelection, setListOccupantSelection] = useState<BuildingMap>(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]);
import { SelectListProps } from "./types";
const Select: React.FC<SelectListProps> = ({ language, query }) => {
const isEmployeee = query?.type == "employee";
const isOccupante = query?.type == "occupant";
return (
<>
<div className="flex h-full min-h-[inherit] flex-col items-center justify-center gap-4">
<div className="w-full max-w-md rounded-lg bg-white p-8 shadow-md">
{isEmployeeTrue && <LoginEmployee translation={translation} selectionList={listEmployeeSelection} lang={language} />}
{isOccupantTrue && <LoginOccupant translation={translation} selectionList={listOccupantSelection} lang={language} />}
{isEmployeee && <LoginEmployee lang={language} />}
{isOccupante && <LoginOccupant lang={language} />}
</div>
</div>
</>

View File

@ -3,18 +3,15 @@ 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<AuthPageProps> = 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 <Select selectionList={selectionList} isEmployee={isEmployee} isOccupant={isOccupant} language={language} query={query} />
try {
return <Select language={language} query={query} />;
} catch (error) {
redirect("/auth/login");
return null;
}
}
export default SelectPage;

View File

@ -1,3 +1,4 @@
import { ClientSelection } from "@/types/mutual/context/validations";
import { LanguageTypes } from "@/validations/mutual/language/validations";
interface Company {
@ -30,22 +31,15 @@ interface BuildingMap {
}
interface SelectListProps {
selectionList: Company[] | BuildingMap;
isEmployee: boolean;
isOccupant: boolean;
language: LanguageTypes;
query?: { [key: string]: string | string[] | undefined };
}
interface LoginOccupantProps {
selectionList: BuildingMap;
translation: any;
lang: LanguageTypes;
}
interface LoginEmployeeProps {
selectionList: Company[];
translation: any;
lang: LanguageTypes;
}

View File

@ -97,14 +97,8 @@ async function coreFetch<T>(
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",

View File

@ -44,9 +44,6 @@ async function listApplicationsAvailable(payload: PaginationParams): Promise<Pag
query: payload.query,
};
console.log('Sending request to backend with service_uu_id:', payload.query.service_uu_id);
console.log('Full request body:', JSON.stringify(requestBody, null, 2));
const response = await fetchDataWithToken(
applicationListAvailableEndpoint,
requestBody,
@ -56,7 +53,6 @@ async function listApplicationsAvailable(payload: PaginationParams): Promise<Pag
if (response?.status === 200 || response?.status === 202) {
const responseData = response.data as PaginatedApiResponse<any>;
console.log('list_events_available responseData:', JSON.stringify(responseData, null, 2));
return {
data: responseData.data || [],
pagination: collectPaginationFromApiResponse(responseData)
@ -93,9 +89,6 @@ async function listApplicationsAppended(payload: PaginationParams): Promise<Pagi
query: payload.query,
};
console.log('Sending request to backend with service_uu_id:', payload.query.service_uu_id);
console.log('Full request body:', JSON.stringify(requestBody, null, 2));
const response = await fetchDataWithToken(
applicationListAppendedEndpoint,
requestBody,
@ -105,7 +98,6 @@ async function listApplicationsAppended(payload: PaginationParams): Promise<Pagi
if (response?.status === 200 || response?.status === 202) {
const responseData = response.data as PaginatedApiResponse<any>;
console.log('list_events_available responseData:', JSON.stringify(responseData, null, 2));
return {
data: responseData.data || [],
pagination: collectPaginationFromApiResponse(responseData)
@ -116,7 +108,6 @@ async function listApplicationsAppended(payload: PaginationParams): Promise<Pagi
pagination: defaultPaginationResponse,
};
} catch (error) {
console.error("Error fetching events list:", error);
return {
data: [],
pagination: defaultPaginationResponse,
@ -134,8 +125,6 @@ async function listAllApplications(payload: PaginationParams): Promise<Paginated
query: payload.query,
};
// console.log('Sending request to backend with service_uu_id:', payload.query.service_uu_id);
// console.log('Full request body:', JSON.stringify(requestBody, null, 2));
const response = await fetchDataWithToken(
applicationListEndpoint,
@ -156,7 +145,6 @@ async function listAllApplications(payload: PaginationParams): Promise<Paginated
pagination: defaultPaginationResponse,
};
} catch (error) {
console.error("Error fetching events list:", error);
return {
data: [],
pagination: defaultPaginationResponse,
@ -174,7 +162,7 @@ async function appendApplicationToService(payload: AppendApplicationToService) {
);
return response?.status === 200 || response?.status === 202 ? response.data : null;
} catch (error) {
console.error("Error appending event to service:", error);
return null;
}
}
@ -188,12 +176,10 @@ async function removeApplicationFromService(payload: RemoveApplicationFromServic
);
return response?.status === 200 || response?.status === 202 ? response.data : null;
} catch (error) {
console.error("Error removing event from service:", error);
}
}
async function createApplication(payload: any) {
console.log("Creating application with payload:", payload);
try {
const response = await fetchDataWithToken(
applicationCreateEndpoint,
@ -211,7 +197,6 @@ async function createApplication(payload: any) {
}
async function updateApplication(payload: any, uuId: string) {
console.log("Updating application with payload:", payload, 'uuId:', uuId);
try {
const response = await fetchDataWithToken(
`${applicationUpdateEndpoint}/${uuId}`,

View File

@ -1,8 +1,8 @@
"use server";
import { fetchDataWithToken } from "../api-fetcher";
import { baseUrlApplication } from "../basics";
import { PaginationParams } from "../schemas/list";
import { fetchDataWithToken } from "@/apicalls/api-fetcher";
import { baseUrlApplication } from "@/apicalls/basics";
import { PaginationParams } from "@/apicalls/schemas/list";
import { PaginatedApiResponse, collectPaginationFromApiResponse, defaultPaginationResponse } from "@/app/api/utils/types";
const eventsListAvailableEndpoint = `${baseUrlApplication}/events/list/available`;
@ -36,9 +36,6 @@ async function list_events_available(payload: PaginationParams): Promise<Paginat
query: payload.query,
};
console.log('Sending request to backend with service_uu_id:', payload.query.service_uu_id);
console.log('Full request body:', JSON.stringify(requestBody, null, 2));
const response = await fetchDataWithToken(
eventsListAvailableEndpoint,
requestBody,
@ -48,7 +45,6 @@ async function list_events_available(payload: PaginationParams): Promise<Paginat
if (response?.status === 200 || response?.status === 202) {
const responseData = response.data as PaginatedApiResponse<any>;
console.log('list_events_available responseData:', JSON.stringify(responseData, null, 2));
return {
data: responseData.data || [],
pagination: collectPaginationFromApiResponse(responseData)
@ -59,7 +55,6 @@ async function list_events_available(payload: PaginationParams): Promise<Paginat
pagination: defaultPaginationResponse,
};
} catch (error) {
console.error("Error fetching events list:", error);
return {
data: [],
pagination: defaultPaginationResponse,

View File

@ -72,10 +72,8 @@ async function retrieveUserSelection() {
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;

41
y-trash/client_frontend copy/.gitignore vendored Normal file
View File

@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@ -0,0 +1,21 @@
FROM node:23-alpine
WORKDIR /
# Copy package.json and package-lock.json
COPY /web_services/client_frontend/package*.json ./web_services/client_frontend/
# Install dependencies
RUN cd ./web_services/client_frontend && npm install --legacy-peer-deps
# Copy the rest of the application
COPY /web_services/client_frontend ./web_services/client_frontend
## Build the Next.js app
#RUN cd ./web_services/client_frontend && npm run dev
# Expose the port the app runs on
EXPOSE 3000
# Command to run the app
CMD ["sh", "-c", "cd /web_services/client_frontend && npm run dev"]

View File

@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

View File

@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

View File

@ -0,0 +1,16 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
export default eslintConfig;

View File

@ -0,0 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
{
"name": "client-frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@hookform/resolvers": "^5.0.1",
"@radix-ui/react-accordion": "^1.2.10",
"@radix-ui/react-alert-dialog": "^1.1.13",
"@radix-ui/react-aspect-ratio": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.9",
"@radix-ui/react-checkbox": "^1.3.1",
"@radix-ui/react-collapsible": "^1.1.10",
"@radix-ui/react-context-menu": "^2.2.14",
"@radix-ui/react-dialog": "^1.1.13",
"@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-hover-card": "^1.1.13",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-menubar": "^1.1.14",
"@radix-ui/react-navigation-menu": "^1.2.12",
"@radix-ui/react-popover": "^1.1.13",
"@radix-ui/react-progress": "^1.1.6",
"@radix-ui/react-radio-group": "^1.3.6",
"@radix-ui/react-scroll-area": "^1.2.8",
"@radix-ui/react-select": "^2.2.4",
"@radix-ui/react-separator": "^1.1.6",
"@radix-ui/react-slider": "^1.3.4",
"@radix-ui/react-slot": "^1.2.2",
"@radix-ui/react-switch": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.11",
"@radix-ui/react-toggle": "^1.1.8",
"@radix-ui/react-tooltip": "^1.2.6",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"flatpickr": "^4.6.13",
"lucide-react": "^0.487.0",
"next": "^15.2.4",
"next-crypto": "^1.0.8",
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-day-picker": "^8.10.1",
"react-dom": "^19.0.0",
"react-hook-form": "^7.56.3",
"sonner": "^2.0.3",
"tailwind-merge": "^3.3.0",
"zod": "^3.24.4"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4.1.6",
"@types/node": "^20.17.46",
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.5",
"eslint": "^9",
"eslint-config-next": "15.3.2",
"tailwindcss": "^4.1.6",
"tw-animate-css": "^1.2.9",
"typescript": "^5.8.3"
}
}

View File

@ -0,0 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
};
export default config;

View File

@ -0,0 +1,15 @@
'use server';
import { MaindasboardPageProps } from "@/validations/mutual/dashboard/props";
import { DashboardLayout } from "@/layouts/dashboard/layout";
const MainEnPage: React.FC<MaindasboardPageProps> = async ({ params, searchParams }) => {
const parameters = await params;
const searchParameters = await searchParams;
return (
<div className="flex flex-col items-center justify-center">
<DashboardLayout params={parameters} searchParams={searchParameters} lang="en" />
</div>
);
}
export default MainEnPage;

View File

@ -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<NextResponse> {
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" });
}
}

View File

@ -0,0 +1,31 @@
import { NextResponse } from "next/server";
export async function POST() {
async function retrieveAvailableApplication(): Promise<string[]> {
return new Promise((resolve) => {
const mockList = [
"/definitions/identifications/people",
"/definitions/identifications/users",
"/definitions/building/parts",
"/definitions/building/areas",
"/building/accounts/managment/accounts",
"/building/accounts/managment/budgets",
"/building/accounts/parts/accounts",
"/building/accounts/parts/budgets",
"/building/meetings/regular/actions",
"/building/meetings/regular/accounts",
"/building/meetings/ergunt/actions",
"/building/meetings/ergunt/accounts",
"/building/meetings/invited/attendance",
"/main/pages/user/dashboard",
];
resolve(mockList);
});
}
const availableApplications = await retrieveAvailableApplication();
return NextResponse.json({
status: 200,
data: availableApplications,
});
}

View File

@ -0,0 +1,15 @@
import { NextResponse } from "next/server";
export async function POST(): Promise<NextResponse> {
async function retrievePageToRender(): Promise<string> {
return new Promise((resolve) => {
resolve("superUserTenantSomething");
});
}
const pageToRender = await retrievePageToRender();
return NextResponse.json({
status: 200,
data: pageToRender,
});
}

View File

@ -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<NextResponse> {
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" });
}
}

View File

@ -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<NextResponse> {
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" });
}
}

View File

@ -0,0 +1,169 @@
import { NextRequest } from "next/server";
import {
successResponse,
errorResponse,
paginationResponse,
createResponse,
updateResponse,
deleteResponse,
} from "./responseHandlers";
import { withErrorHandling, validateRequiredFields } from "./requestHandlers";
import {
ApiHandler,
PaginationParams,
ListFunction,
CreateFunction,
UpdateFunction,
DeleteFunction,
} from "./types";
/**
* Generic list operation handler
* @param request NextRequest object
* @param body Request body
* @param listFunction The function to call to get the list data
*/
export async function handleListOperation(
request: NextRequest,
body: any,
listFunction: ListFunction
) {
const page = body.page || 1;
const size = body.size || 10;
const orderField = body.orderField || ["uu_id"];
const orderType = body.orderType || ["asc"];
const query = body.query || {};
const response = await listFunction({
page,
size,
orderField,
orderType,
query,
} as PaginationParams);
return paginationResponse(response.data, response.pagination);
}
/**
* Generic create operation handler
* @param request NextRequest object
* @param body Request body
* @param createFunction The function to call to create the item
* @param requiredFields Array of required field names
*/
export async function handleCreateOperation(
body: any,
createFunction?: CreateFunction,
requiredFields: string[] = []
) {
if (requiredFields.length > 0) {
const validation = validateRequiredFields(body, requiredFields);
if (!validation.valid) {
return errorResponse(validation.error as string, 400);
}
}
if (createFunction) {
console.log("Body:", body);
const result = await createFunction(body);
return createResponse(result);
}
return createResponse({
uuid: Math.floor(Math.random() * 1000),
...body,
});
}
/**
* Generic update operation handler
* @param request NextRequest object
* @param body Request body
* @param updateFunction The function to call to update the item
*/
export async function handleUpdateOperation(
request: NextRequest,
body: any,
updateFunction?: UpdateFunction
) {
const uuid = request.nextUrl.searchParams.get("uuid");
if (!uuid) {
return errorResponse("UUID not found", 400);
}
if (updateFunction) {
console.log("Body:", body);
const result = await updateFunction(body, uuid);
return updateResponse(result);
}
return updateResponse(body);
}
/**
* Generic delete operation handler
* @param request NextRequest object
* @param deleteFunction The function to call to delete the item
*/
export async function handleDeleteOperation(
request: NextRequest,
deleteFunction?: DeleteFunction
) {
const uuid = request.nextUrl.searchParams.get("uuid");
if (!uuid) {
return errorResponse("UUID not found", 400);
}
if (deleteFunction) {
await deleteFunction(uuid);
}
return deleteResponse();
}
/**
* Create a wrapped list handler with error handling
* @param listFunction The function to call to get the list data
*/
export function createListHandler(listFunction: ListFunction) {
return withErrorHandling((request: NextRequest, body: any) =>
handleListOperation(request, body, listFunction)
);
}
/**
* Create a wrapped create handler with error handling
* @param createFunction The function to call to create the item
* @param requiredFields Array of required field names
*/
export function createCreateHandler(
createFunction?: CreateFunction,
requiredFields: string[] = []
) {
console.log("Required fields:", requiredFields);
// This handler only takes the body parameter, not the request
return withErrorHandling((body: any) => {
// Ensure we're only passing the actual body data to the create function
if (body && typeof body === 'object' && body.body) {
console.log("Extracting body from request body");
return handleCreateOperation(body.body, createFunction, requiredFields);
}
return handleCreateOperation(body, createFunction, requiredFields);
});
}
/**
* Create a wrapped update handler with error handling
* @param updateFunction The function to call to update the item
*/
export function createUpdateHandler(updateFunction?: UpdateFunction) {
return withErrorHandling((request: NextRequest, body: any) =>
handleUpdateOperation(request, body, updateFunction)
);
}
/**
* Create a wrapped delete handler with error handling
* @param deleteFunction The function to call to delete the item
*/
export function createDeleteHandler(deleteFunction?: DeleteFunction) {
return withErrorHandling((request: NextRequest) =>
handleDeleteOperation(request, deleteFunction)
);
}

View File

@ -0,0 +1,4 @@
// Export all utility functions from a single entry point
export * from './responseHandlers';
export * from './requestHandlers';
export * from './apiOperations';

View File

@ -0,0 +1,70 @@
import { NextRequest } from "next/server";
import { errorResponse } from "./responseHandlers";
import { ValidationResult, ApiHandler, ApiHandlerBodyOnly, ApiHandlerWithRequest } from "./types";
/**
* Safely parse JSON request body with error handling
* @param request NextRequest object
* @returns Parsed request body or null if parsing fails
*/
export async function parseRequestBody(request: NextRequest) {
try {
return await request.json();
} catch (error) {
return null;
}
}
/**
* Wrapper for API route handlers with built-in error handling
* @param handler The handler function to wrap
*/
export function withErrorHandling(
handler: ApiHandler
) {
return async (request: NextRequest) => {
try {
const body = await parseRequestBody(request);
if (body === null) {
return errorResponse("Invalid request body", 400);
}
// Check handler parameter count to determine if it needs request object
// If handler has only 1 parameter, it's likely a create operation that only needs body
if (handler.length === 1) {
// Cast to the appropriate handler type
return await (handler as ApiHandlerBodyOnly)(body);
} else {
// Otherwise pass both request and body (for list, update, delete operations)
return await (handler as ApiHandlerWithRequest)(request, body);
}
} catch (error: any) {
return errorResponse(
error.message || "Internal Server Error",
error.status || 500
);
}
};
}
/**
* Validate that required fields are present in the request body
* @param body Request body
* @param requiredFields Array of required field names
* @returns Object with validation result and error message if validation fails
*/
export function validateRequiredFields(body: any, requiredFields: string[]): ValidationResult {
const missingFields = requiredFields.filter(field =>
body[field] === undefined || body[field] === null || body[field] === ''
);
if (missingFields.length > 0) {
return {
valid: false,
error: `Missing required fields: ${missingFields.join(', ')}`
};
}
return { valid: true };
}

View File

@ -0,0 +1,91 @@
import { NextResponse } from "next/server";
import { ApiResponse, PaginationResponse, PaginatedApiResponse } from "./types";
/**
* Standard success response handler
* @param data The data to return in the response
* @param status HTTP status code (default: 200)
*/
export function successResponse<T>(data: T, status: number = 200) {
return NextResponse.json(
{
success: true,
data,
} as ApiResponse<T>,
{ status }
);
}
/**
* Standard error response handler
* @param message Error message
* @param status HTTP status code (default: 500)
*/
export function errorResponse(message: string, status: number = 500) {
console.error(`API error: ${message}`);
return NextResponse.json(
{
success: false,
error: message
} as ApiResponse<never>,
{ status }
);
}
/**
* Standard pagination response format
* @param data Array of items to return
* @param pagination Pagination information
*/
export function paginationResponse<T>(data: T[], pagination: PaginationResponse | null) {
return NextResponse.json({
data: data || [],
pagination: pagination || {
page: 1,
size: 10,
totalCount: 0,
totalItems: 0,
totalPages: 0,
pageCount: 0,
orderField: ["name"],
orderType: ["asc"],
query: {},
next: false,
back: false,
},
} as PaginatedApiResponse<T>);
}
/**
* Create response handler
* @param data The created item data
*/
export function createResponse<T>(data: T) {
return successResponse(
{
...data as any,
createdAt: new Date().toISOString(),
} as T,
201
);
}
/**
* Update response handler
* @param data The updated item data
*/
export function updateResponse<T>(data: T) {
return successResponse(
{
...data as any,
updatedAt: new Date().toISOString(),
} as T
);
}
/**
* Delete response handler
*/
export function deleteResponse() {
return successResponse({ message: "Item deleted successfully" }, 204);
}

View File

@ -0,0 +1,119 @@
/**
* Type definitions for API utilities
*/
import { NextRequest } from "next/server";
/**
* Validation result interface
*/
export interface ValidationResult {
valid: boolean;
error?: string;
}
/**
* Pagination parameters interface
*/
export interface PaginationParams {
page: number;
size: number;
orderField: string[];
orderType: string[];
query: Record<string, any>;
}
/**
* Pagination response interface
*/
export interface PaginationResponse {
page: number;
size: number;
totalCount: number;
totalItems: number;
totalPages: number;
pageCount: number;
orderField: string[];
orderType: string[];
query: Record<string, any>;
next: boolean;
back: boolean;
}
export const defaultPaginationResponse: PaginationResponse = {
page: 1,
size: 10,
totalCount: 0,
totalItems: 0,
totalPages: 0,
pageCount: 0,
orderField: ["uu_id"],
orderType: ["asc"],
query: {},
next: false,
back: false,
};
/**
* API response interface
*/
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
}
/**
* Paginated API response interface
*/
export interface PaginatedApiResponse<T> {
data: T[];
pagination: PaginationResponse;
}
export const collectPaginationFromApiResponse = (
response: PaginatedApiResponse<any>
): PaginationResponse => {
return {
page: response.pagination?.page || 1,
size: response.pagination?.size || 10,
totalCount: response.pagination?.totalCount || 0,
totalItems: response.pagination?.totalItems || 0,
totalPages: response.pagination?.totalPages || 0,
pageCount: response.pagination?.pageCount || 0,
orderField: response.pagination?.orderField || ["uu_id"],
orderType: response.pagination?.orderType || ["asc"],
query: response.pagination?.query || {},
next: response.pagination?.next || false,
back: response.pagination?.back || false,
};
};
/**
* API handler function types
*/
export type ApiHandlerWithRequest = (request: NextRequest, body: any) => Promise<Response>;
export type ApiHandlerBodyOnly = (body: any) => Promise<Response>;
export type ApiHandler = ApiHandlerWithRequest | ApiHandlerBodyOnly;
/**
* List function type
*/
export type ListFunction = (
params: PaginationParams
) => Promise<PaginatedApiResponse<any>>;
/**
* Create function type
*/
export type CreateFunction = (data: any) => Promise<any>;
/**
* Update function type
*/
export type UpdateFunction = (id: any, data: any) => Promise<any>;
/**
* Delete function type
*/
export type DeleteFunction = (id: any) => Promise<any>;

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,122 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

View File

@ -0,0 +1,34 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}

View File

@ -0,0 +1,32 @@
"use server";
import Link from "next/link";
export default async function Home() {
const currentDate = new Date().toLocaleString("tr-TR", { timeZone: "Europe/Istanbul" });
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-b from-blue-50 to-white p-8">
<div className="w-full max-w-4xl bg-white rounded-xl shadow-lg overflow-hidden">
<div className="bg-blue-600 text-white p-8 text-center">
<h1 className="text-4xl font-bold mb-2">Welcome to EVYOS</h1>
<p className="text-xl">Enterprise Management System</p>
<p className="text-sm mt-4">Server Time: {currentDate}</p>
</div>
<div className="p-8">
<div className="bg-white rounded-lg p-6 border border-gray-200">
<Link
href="/auth/en/login"
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
>Go to Sign In</Link>
</div>
<div className="mt-6 text-center text-sm text-gray-600">
<p>© {new Date().getFullYear()} EVYOS. All rights reserved.</p>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,11 @@
import { ContentProps } from "@/validations/mutual/dashboard/props";
import { resolveWhichPageToRenderMulti } from "@/pages/resolver/resolver";
import ContentToRenderNoPage from "@/pages/mutual/noContent/page";
const PageToBeChildrendMulti: React.FC<ContentProps> = async ({ lang, translations, activePageUrl, mode }) => {
const ApplicationToRender = await resolveWhichPageToRenderMulti({ activePageUrl })
if (!ApplicationToRender) return <ContentToRenderNoPage lang={lang} />
return <ApplicationToRender lang={lang} translations={translations} activePageUrl={activePageUrl} mode={mode} />
}
export default PageToBeChildrendMulti

View File

@ -0,0 +1,13 @@
import { ContentProps } from "@/validations/mutual/dashboard/props";
import ContentToRenderNoPage from "@/pages/mutual/noContent/page";
import { resolveWhichPageToRenderSingle } from "@/pages/resolver/resolver";
const PageToBeChildrendSingle: React.FC<ContentProps> = async ({ lang, translations, activePageUrl, mode }) => {
const ApplicationToRender = await resolveWhichPageToRenderSingle({ activePageUrl })
if (ApplicationToRender) {
return <ApplicationToRender lang={lang} translations={translations} activePageUrl={activePageUrl} mode={mode} />
}
return <ContentToRenderNoPage lang={lang} />
}
export default PageToBeChildrendSingle

Some files were not shown because too many files have changed in this diff Show More