Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions app/appwrite/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@ import { ID, OAuthProvider, Query } from "appwrite";
import { account, database, appwriteConfig } from "~/appwrite/client";
import { redirect } from "react-router";

/**
* Helper function to query user documents by accountID
*/
const queryUserByAccountId = async (
accountId: string,
selectFields?: string[]
) => {
const queries = [Query.equal("accountID", accountId)];
if (selectFields && selectFields.length > 0) {
queries.push(Query.select(selectFields));
}

return await database.listDocuments(
appwriteConfig.databaseID!,
appwriteConfig.userCollectionID!,
queries
);
};

export const getExistingUser = async (id: string) => {
try {
const { documents, total } = await database.listDocuments(
appwriteConfig.databaseID!,
appwriteConfig.userCollectionID!,
[Query.equal("accountID", id)]
);
const { documents, total } = await queryUserByAccountId(id);
return total > 0 ? documents[0] : null;
} catch (error) {
console.error("Error fetching user:", error);
Expand Down Expand Up @@ -86,13 +101,9 @@ export const getUser = async () => {
const user = await account.get();
if (!user) return redirect("/sign-in");

const { documents } = await database.listDocuments(
appwriteConfig.databaseID!,
appwriteConfig.userCollectionID!,
[
Query.equal("accountID", user.$id),
Query.select(["name", "email", "imageUrl", "joinedAt", "accountID"]),
]
const { documents } = await queryUserByAccountId(
user.$id,
["name", "email", "imageUrl", "joinedAt", "accountID"]
);

return documents.length > 0 ? documents[0] : redirect("/sign-in");
Expand Down
48 changes: 48 additions & 0 deletions app/hooks/useSyncfusionComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useState, useEffect, useRef } from "react";

/**
* Custom hook to dynamically import Syncfusion components
* @param importFn - Function that returns a promise with the Syncfusion package
* @param componentNames - Array of component names to extract from the package
* @returns Object with loaded components or null if not yet loaded
*
* @example
* const components = useSyncfusionComponent(
* () => import("@syncfusion/ej2-react-buttons"),
* ["ButtonComponent"]
* );
* if (!components) return null;
* const { ButtonComponent } = components;
*/
export function useSyncfusionComponent<T extends Record<string, any>>(
importFn: () => Promise<T>,
componentNames: (keyof T)[]
): Record<string, any> | null {
const [components, setComponents] = useState<Record<string, any> | null>(null);
const componentNamesRef = useRef(componentNames);

useEffect(() => {
let isMounted = true;

importFn()
.then((pkg) => {
if (isMounted) {
const loadedComponents: Record<string, any> = {};
componentNamesRef.current.forEach((name) => {
loadedComponents[name as string] = pkg[name];
});
setComponents(loadedComponents);
}
})
.catch((error) => {
console.error("Failed to load Syncfusion component:", error);
});

return () => {
isMounted = false;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return components;
}
14 changes: 14 additions & 0 deletions app/lib/syncfusion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { registerLicense } from "@syncfusion/ej2-base";

/**
* Initializes Syncfusion license
* Should be called once at application startup
*/
let isLicenseRegistered = false;

export function initializeSyncfusionLicense(): void {
if (!isLicenseRegistered) {
registerLicense(import.meta.env.VITE_SYNCFUSION_LICENSE_KEY);
isLicenseRegistered = true;
}
}
16 changes: 7 additions & 9 deletions app/routes/admin/admin-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Outlet, redirect } from "react-router";
import { MobileSideBar, NavItems } from "../../../components/index";
import { useEffect, useState } from "react";
import { useSyncfusionComponent } from "~/hooks/useSyncfusionComponent";
import { account } from "~/appwrite/client";
import { getExistingUser, storeUserData } from "~/appwrite/auth";

Expand Down Expand Up @@ -51,15 +51,13 @@ export async function clientLoader() {
}
}
const AdminLayout = () => {
const [SidebarComponent, setSidebarComponent] = useState<any>(null);

useEffect(() => {
import("@syncfusion/ej2-react-navigations").then((pkg) => {
setSidebarComponent(() => pkg.SidebarComponent);
});
}, []);
const components = useSyncfusionComponent(
() => import("@syncfusion/ej2-react-navigations"),
["SidebarComponent"]
);

if (!SidebarComponent) return null;
if (!components) return null;
const { SidebarComponent } = components;

return (
<div className="admin-layout">
Expand Down
22 changes: 7 additions & 15 deletions app/routes/admin/all-users.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import { Header } from "../../../components";
import { cn, formatDate } from "~/lib/utils";
import { getAllUsers } from "~/appwrite/auth";
import { useSyncfusionComponent } from "~/hooks/useSyncfusionComponent";
import type { Route } from "./+types/all-users";


Expand All @@ -12,22 +12,14 @@ export const loader = async () => {
}

const Allusers = ({loaderData}: Route.ComponentProps) => {
const [GridComponent, setGridComponent] = useState<any>(null)
const [ColumnsDirective, setColumnsDirective] = useState<any>(null)
const [ColumnDirective, setColumnDirective] = useState<any>(null)

const { users } = loaderData;
const components = useSyncfusionComponent(
() => import("@syncfusion/ej2-react-grids"),
["GridComponent", "ColumnsDirective", "ColumnDirective"]
);


useEffect(() => {
import("@syncfusion/ej2-react-grids").then((pkg) => {
setGridComponent(() => pkg.GridComponent)
setColumnsDirective(() => pkg.ColumnsDirective)
setColumnDirective(() => pkg.ColumnDirective)
})
}, [])

if (!GridComponent || !ColumnsDirective) return null;
if (!components) return null;
const { GridComponent, ColumnsDirective, ColumnDirective } = components;
return (
<main className='all-users wrapper'>
<Header
Expand Down
16 changes: 7 additions & 9 deletions app/routes/admin/create-trip.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
import { Header } from '../../../components'
import { useSyncfusionComponent } from "~/hooks/useSyncfusionComponent";
import type { Route } from './+types/create-trip';


Expand All @@ -18,8 +18,6 @@ export const loader = async () => {
}

const CreateTrip = ({ loaderData }: Route.ComponentProps) => {
const [ComboBoxComponent, setComboBoxComponent] = useState<any>(null);

const handleSubmit = async () => { };
const handleChange = (key: keyof TripFormData, value: string | number) => { }

Expand All @@ -30,13 +28,13 @@ const CreateTrip = ({ loaderData }: Route.ComponentProps) => {
value: country.value,
}));

useEffect(() => {
import("@syncfusion/ej2-react-dropdowns").then((pkg) => {
setComboBoxComponent(() => pkg.ComboBoxComponent)
})
}, []);
const components = useSyncfusionComponent(
() => import("@syncfusion/ej2-react-dropdowns"),
["ComboBoxComponent"]
);

if (!ComboBoxComponent) return null
if (!components) return null;
const { ComboBoxComponent } = components;

return (
<main className='flex flex-col gap-10 pb-20 wrapper'>
Expand Down
4 changes: 2 additions & 2 deletions app/routes/main.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import { initializeSyncfusionLicense } from "~/lib/syncfusion";

import { registerLicense } from "@syncfusion/ej2-base";
registerLicense(import.meta.env.VITE_SYNCFUSION_LICENSE_KEY);
initializeSyncfusionLicense();

const MainPage = () => {
return (
Expand Down
18 changes: 8 additions & 10 deletions components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
import { Link, useLocation } from "react-router";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils";
import { useSyncfusionComponent } from "~/hooks/useSyncfusionComponent";

type Props = {
title: string;
Expand All @@ -19,16 +19,14 @@ type Props = {
* @returns {JSX.Element} The rendered Header component.
*/
const Header = ({ title, descprition, ctaText, ctaUrl }: Props) => {
const [ButtonComponent, setButtonComponent] = useState<any>(null)
const location = useLocation();
const components = useSyncfusionComponent(
() => import("@syncfusion/ej2-react-buttons"),
["ButtonComponent"]
);

useEffect(() => {
import("@syncfusion/ej2-react-buttons").then((pkg) => (
setButtonComponent(() => pkg.ButtonComponent)
))
});

if(!ButtonComponent) return null;
if (!components) return null;
const { ButtonComponent } = components;

return (
<header className="header">
Expand Down
22 changes: 10 additions & 12 deletions components/MobileSideBar.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import { useRef, useState, useEffect } from "react";
import { useRef } from "react";
import { Link } from "react-router";
import NavItems from "./NavItems";
import { useSyncfusionComponent } from "~/hooks/useSyncfusionComponent";
import { initializeSyncfusionLicense } from "~/lib/syncfusion";

import { registerLicense } from "@syncfusion/ej2-base";

registerLicense(import.meta.env.VITE_SYNCFUSION_LICENSE_KEY);
initializeSyncfusionLicense();

const MobileSideBar = () => {
const [SidebarComponent, setSidebarComponent] = useState<any>(null);
const sideBarRef = useRef<any>(null);
const components = useSyncfusionComponent(
() => import("@syncfusion/ej2-react-navigations"),
["SidebarComponent"]
);

const toggleSidebar = () => {
sideBarRef.current?.toggle();
};

useEffect(() => {
import("@syncfusion/ej2-react-navigations").then((pkg) => {
setSidebarComponent(() => pkg.SidebarComponent);
});
}, []);

if (!SidebarComponent) return null;
if (!components) return null;
const { SidebarComponent } = components;

return (
<div className="mobile-sidebar">
Expand Down
4 changes: 2 additions & 2 deletions components/NavItems.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Link, NavLink, useLoaderData, useNavigate } from "react-router";
import { sidebarItems } from "~/constants";
import { cn } from "~/lib/utils";
import { registerLicense } from "@syncfusion/ej2-base";
import { initializeSyncfusionLicense } from "~/lib/syncfusion";
import { logoutUser } from "~/appwrite/auth";

registerLicense(import.meta.env.VITE_SYNCFUSION_LICENSE_KEY);
initializeSyncfusionLicense();

const NavItems = ({ handleClick }: { handleClick?: () => void }) => {
const user = useLoaderData();
Expand Down
20 changes: 7 additions & 13 deletions components/TripCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
import { Link, useLocation } from "react-router";
import { getFirstWord, cn } from "~/lib/utils";
import { useSyncfusionComponent } from "~/hooks/useSyncfusionComponent";

const TripCard = ({
id,
Expand All @@ -10,20 +10,14 @@ const TripCard = ({
tags,
price,
}: TripCardProps) => {
const [ChipListComponent, setChipListComponent] = useState<any>(null);
const [ChipsDirective, setChipsDirective] = useState<any>(null);
const [ChipDirective, setChipDirective] = useState<any>(null);
const path = useLocation();
const components = useSyncfusionComponent(
() => import("@syncfusion/ej2-react-buttons"),
["ChipListComponent", "ChipsDirective", "ChipDirective"]
);

useEffect(() => {
import("@syncfusion/ej2-react-buttons").then((pkg) => {
setChipListComponent(() => pkg.ChipListComponent);
setChipsDirective(() => pkg.ChipsDirective);
setChipDirective(() => pkg.ChipDirective);
});
}, []);

if (!ChipListComponent || !ChipsDirective || !ChipDirective) return null;
if (!components) return null;
const { ChipListComponent, ChipsDirective, ChipDirective } = components;

return (
<Link
Expand Down
Loading