diff --git a/.github/workflows/sahil-pay.yml b/.github/sahil-pay.yml similarity index 100% rename from .github/workflows/sahil-pay.yml rename to .github/sahil-pay.yml diff --git a/.github/workflows/sahil-website.yml b/.github/workflows/sahil-website.yml index 16cdea96..b36ce264 100644 --- a/.github/workflows/sahil-website.yml +++ b/.github/workflows/sahil-website.yml @@ -42,6 +42,21 @@ on: description: DynamoDB table for State lock default: "sahil-terraform-table-locks" type: string + push: + branches: ["develop", "main"] + +env: + PATH_TO_DOCKERFILE: infra/docker/Dockerfile.website + DOCKER_BUILD_DIR: . + IMAGE_TAG: sahil-website + LIFECYCLE_POLICY_FILE: policy.json + BACKEND_S3_BUCKET: sahil-terraform-state-bucket + BACKEND_IAM_ROLE: workload-assumable-role + GITHUB_IAM_ROLE: github-actions-role + AWS_ACCOUNT_ID: 060795911441 + AWS_REGION: eu-west-1 + BACKEND_DYNAMODB_TABLE: sahil-terraform-table-locks + # concurrency required to avoid terraform lock contention during ECR provisioning concurrency: ci-${{ github.repository }}-website-docker-pipeline diff --git a/apps/admin/src/Layout/layout.tsx b/apps/admin/src/Layout/layout.tsx index ff7e0f2a..fedaf064 100644 --- a/apps/admin/src/Layout/layout.tsx +++ b/apps/admin/src/Layout/layout.tsx @@ -1,19 +1,33 @@ -import React, { ReactNode } from "react"; +import React, { ReactNode, useEffect } from "react"; import logo from "../../public/logo-alt.svg"; import { useRouter } from "next/router"; import { signOut, useSession } from "next-auth/react"; -import { Navbar } from "ui"; -type LayoutProps = { - children: ReactNode; -}; +import NProgress from "nprogress"; +import "nprogress/nprogress.css"; +import { UserProvider } from '@sahil/features/auth/UserContext'; +import { useGetUserById } from "@sahil/lib/hooks/users"; +import { SplashScreen, Navbar, ContentLayout } from "ui"; + import { - HiOutlineUsers, - HiOutlineQueueList, - HiOutlineMap, + HiOutlineBriefcase, + HiOutlineCube, + HiOutlineTruck, + HiOutlineBuildingStorefront, + HiMiniArrowLeftCircle, HiArrowPath, HiOutlineIdentification, + HiOutlineMap, + HiOutlineUsers, HiOutlineBuildingOffice2, + HiOutlineQueueList } from "react-icons/hi2"; +import { Button } from "ui"; + +type LayoutProps = { + children: ReactNode; +}; + + const links = [ { name: "Users", @@ -42,26 +56,77 @@ const links = [ }, ]; + export default function Layout({ children, ...props }: LayoutProps) { const router = useRouter(); const { data: session } = useSession(); + const isAuthRoute = router.pathname.startsWith('/auth'); + + const { data: currentUser, loading: userLoading } = useGetUserById(session?.user?.id); + useEffect(() => { + NProgress.configure({ + showSpinner: false, + trickleSpeed: 200, + minimum: 0.08 + }); + + const handleStart = () => { + NProgress.start(); + }; + + const handleStop = () => { + NProgress.done(); + }; + + router.events.on('routeChangeStart', handleStart); + router.events.on('routeChangeComplete', handleStop); + router.events.on('routeChangeError', handleStop); + + return () => { + router.events.off('routeChangeStart', handleStart); + router.events.off('routeChangeComplete', handleStop); + router.events.off('routeChangeError', handleStop); + }; + }, [router]); const onSignOut = async () => { await signOut(); router.push("/auth/signin"); }; + + const handleBack = () => { + router.back(); + }; + + const handleRefresh = () => { + router.replace(router.asPath); + }; + + if (!session && !isAuthRoute) { + return ; + } + return ( - <> - {session?.user && ( + - )} -
{children}
- + + {children} + +
); } diff --git a/apps/admin/src/pages/agents/[agentId].tsx b/apps/admin/src/pages/agents/[agentId].tsx new file mode 100644 index 00000000..765e9edc --- /dev/null +++ b/apps/admin/src/pages/agents/[agentId].tsx @@ -0,0 +1,3 @@ +export default function AgentPage() { + return
Agent
; +} \ No newline at end of file diff --git a/apps/admin/src/pages/agents/index.tsx b/apps/admin/src/pages/agents/index.tsx index 7f6eeb30..e1210a3a 100644 --- a/apps/admin/src/pages/agents/index.tsx +++ b/apps/admin/src/pages/agents/index.tsx @@ -1,9 +1,25 @@ +import { useRouter } from "next/router"; +import { HiPlus, HiOutlineDocumentMagnifyingGlass } from "react-icons/hi2"; +import { ListAgents } from "@sahil/features/Agents/ListAgents"; + export default function Agents() { + const router = useRouter(); return ( -
-
-

Agents Page

+
+
+
+

Agent

+
+
+ +
+
); } diff --git a/apps/admin/src/pages/clients/[clientId].tsx b/apps/admin/src/pages/clients/[clientId].tsx new file mode 100644 index 00000000..53c8e8b5 --- /dev/null +++ b/apps/admin/src/pages/clients/[clientId].tsx @@ -0,0 +1,100 @@ +import { useRouter } from "next/router"; +import { useFetchSupplierByPK } from "@sahil/lib/hooks/suppliers"; +import { useFetchBusinessByPK } from "@sahil/lib/hooks/businesses"; +import { + SupplierOrderHistory, + SupplierProducts, + ServiceZones, + SupplierProfileOverview, +} from "@sahil/features/Suppliers"; +import { + BusinessProfileOverview, + BusinessOrderHistory, +} from "@sahil/features/businesses"; +import { Business, Suppliers } from "@sahil/lib/graphql/__generated__/graphql"; + +export default function ClientPage() { + const router = useRouter(); + const { clientId, type } = router.query; + const isSupplier = type === 'supplier'; + + if (!clientId || !type) { + return
Invalid client details
; + } + + if (isSupplier) { + return ; + } + + return ; +} + +function SupplierView({ clientId }: { clientId: string }) { + const { + data: supplier, + error: supplierError, + loading: supplierLoading + } = useFetchSupplierByPK(clientId); + + if (supplierLoading) { + return

Loading...

; + } + + if (supplierError) { + return
Error loading supplier details
; + } + + if (!supplier) { + return
Supplier not found
; + } + + return ( +
+
+
+ + +
+
+ + +
+
+
+ ); +} + +function BusinessView({ clientId }: { clientId: string }) { + const { + data: business, + error: businessError, + loading: businessLoading + } = useFetchBusinessByPK(clientId); + + if (businessLoading) { + return

Loading...

; + } + + if (businessError) { + return
Error loading business details
; + } + + if (!business) { + return
Business not found
; + } + + return ( +
+
+
+ +
+
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/apps/admin/src/pages/clients/index.tsx b/apps/admin/src/pages/clients/index.tsx index 7a4c4be6..ceabfe8f 100644 --- a/apps/admin/src/pages/clients/index.tsx +++ b/apps/admin/src/pages/clients/index.tsx @@ -1,9 +1,13 @@ +import { useRouter } from "next/router"; +import { HiPlus, HiOutlineDocumentMagnifyingGlass } from "react-icons/hi2"; +import { ListClients } from "@sahil/features/Clients/ListClients"; + export default function Clients() { + const router = useRouter(); + return ( -
-
-

Clients Page

-
-
+
+ +
); } diff --git a/apps/admin/src/pages/notifications/index.tsx b/apps/admin/src/pages/notifications/index.tsx new file mode 100644 index 00000000..03359b0b --- /dev/null +++ b/apps/admin/src/pages/notifications/index.tsx @@ -0,0 +1,9 @@ +import { ListNotifications } from "@sahil/features/Notifications/ListNotifications"; + +export default function Notifications() { + return ( +
+ +
+ ); +} diff --git a/apps/admin/src/pages/orders/[orderId].tsx b/apps/admin/src/pages/orders/[orderId].tsx new file mode 100644 index 00000000..d4745d3b --- /dev/null +++ b/apps/admin/src/pages/orders/[orderId].tsx @@ -0,0 +1,3 @@ +export default function OrderPage() { + return
Order
; +} \ No newline at end of file diff --git a/apps/admin/src/pages/orders/index.tsx b/apps/admin/src/pages/orders/index.tsx index 55fd1bbd..331a7714 100644 --- a/apps/admin/src/pages/orders/index.tsx +++ b/apps/admin/src/pages/orders/index.tsx @@ -1,9 +1,32 @@ +import { useRouter } from "next/router"; +import { HiPlus, HiOutlineDocumentMagnifyingGlass } from "react-icons/hi2"; +import { ListOrders } from "@sahil/features/Orders/ListOrders"; +import { Card, Stats, Stat } from "ui"; + export default function Orders() { + const router = useRouter(); return ( -
-
-

Orders Page

-
+
+ +
+
+

Orders

+
+
+ + +
+
+
+
); } diff --git a/apps/admin/src/pages/zones/[zoneId].tsx b/apps/admin/src/pages/zones/[zoneId].tsx new file mode 100644 index 00000000..7e114996 --- /dev/null +++ b/apps/admin/src/pages/zones/[zoneId].tsx @@ -0,0 +1,3 @@ +export default function ZonePage() { + return
Zone
; +} \ No newline at end of file diff --git a/apps/admin/src/pages/zones/index.tsx b/apps/admin/src/pages/zones/index.tsx index 11537551..5b23ce77 100644 --- a/apps/admin/src/pages/zones/index.tsx +++ b/apps/admin/src/pages/zones/index.tsx @@ -1,9 +1,25 @@ +import { useRouter } from "next/router"; +import { HiPlus, HiOutlineDocumentMagnifyingGlass } from "react-icons/hi2"; +import { ListZones } from "@sahil/features/Zones/ListZones"; + export default function Zones() { + const router = useRouter(); return ( -
-
-

Zones Page

+
+
+
+

Zones

+
+
+ +
+
); } diff --git a/apps/admin/src/pages/zones/new.tsx b/apps/admin/src/pages/zones/new.tsx new file mode 100644 index 00000000..2fa07df6 --- /dev/null +++ b/apps/admin/src/pages/zones/new.tsx @@ -0,0 +1,3 @@ +export default function NewZonePage() { + return
New Zone Page
; +} \ No newline at end of file diff --git a/apps/agent/package.json b/apps/agent/package.json index 7e833b1d..7ec7e487 100644 --- a/apps/agent/package.json +++ b/apps/agent/package.json @@ -30,6 +30,7 @@ "next": "^14.2.3", "next-auth": "^4.24.7", "next-auth-hasura-adapter": "^2.0.0", + "nprogress": "^0.2.0", "postcss": "8.4.28", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -47,6 +48,7 @@ "devDependencies": { "@apollo/link-schema": "^2.0.0-beta.3", "@types/node": "^20.8.10", + "@types/nprogress": "^0.2.3", "@types/react": "^18.2.33", "@types/react-dom": "^18.2.14", "@welldone-software/why-did-you-render": "^8.0.1", diff --git a/apps/agent/src/Dashboard/AgentToolBar.tsx b/apps/agent/src/Dashboard/AgentToolBar.tsx index 51d2381f..f1e4aa80 100644 --- a/apps/agent/src/Dashboard/AgentToolBar.tsx +++ b/apps/agent/src/Dashboard/AgentToolBar.tsx @@ -3,9 +3,12 @@ import { HiChevronDown, HiOutlineTruck, HiOutlineBriefcase, + HiOutlineBuildingOffice, } from "react-icons/hi2"; import Link from "next/link"; -import { Avatar } from "ui"; + +import { Avatar, Card, Dropdown } from "ui"; + import { useSession } from "next-auth/react"; import { formatCurrentDate } from "@sahil/lib/dates"; diff --git a/apps/agent/src/Layout/layout.tsx b/apps/agent/src/Layout/layout.tsx index fbd9a357..ad4eda43 100644 --- a/apps/agent/src/Layout/layout.tsx +++ b/apps/agent/src/Layout/layout.tsx @@ -1,11 +1,14 @@ -import React, { ReactNode } from "react"; +import React, { ReactNode, useEffect } from "react"; import logo from "../../public/logo-alt.svg"; import { useRouter } from "next/router"; import { signOut, useSession } from "next-auth/react"; -import { Navbar } from "ui"; -type LayoutProps = { - children: ReactNode; -}; +import { ContentLayout, Navbar } from "ui"; +import NProgress from "nprogress"; +import "nprogress/nprogress.css"; +import { UserProvider } from '@sahil/features/auth/UserContext'; +import { useGetUserById } from "@sahil/lib/hooks/users"; +import { SplashScreen } from "ui"; + import { HiOutlineBriefcase, HiOutlineCube, @@ -13,6 +16,10 @@ import { HiOutlineBuildingStorefront, } from "react-icons/hi2"; +type LayoutProps = { + children: ReactNode; +}; + const links = [ { name: "Businesses", @@ -36,26 +43,78 @@ const links = [ }, ]; + + export default function Layout({ children, ...props }: LayoutProps) { const router = useRouter(); const { data: session } = useSession(); + const isAuthRoute = router.pathname.startsWith('/auth'); + + const { data: currentUser, loading: userLoading } = useGetUserById(session?.user?.id); + useEffect(() => { + NProgress.configure({ + showSpinner: false, + trickleSpeed: 200, + minimum: 0.08 + }); + + const handleStart = () => { + NProgress.start(); + }; + + const handleStop = () => { + NProgress.done(); + }; + + router.events.on('routeChangeStart', handleStart); + router.events.on('routeChangeComplete', handleStop); + router.events.on('routeChangeError', handleStop); + + return () => { + router.events.off('routeChangeStart', handleStart); + router.events.off('routeChangeComplete', handleStop); + router.events.off('routeChangeError', handleStop); + }; + }, [router]); const onSignOut = async () => { await signOut(); router.push("/auth/signin"); }; + + const handleBack = () => { + router.back(); + }; + + const handleRefresh = () => { + router.replace(router.asPath); + }; + + + if (!session && !isAuthRoute) { + return ; + } return ( - <> - {session?.user && ( + - )} -
{children}
- + links={links} + logo={logo} + header="Agent" + onSignOut={onSignOut} + user={{ + ...session?.user, + ...currentUser + }} + /> + + {children} + +
); } diff --git a/apps/agent/src/pages/_app.tsx b/apps/agent/src/pages/_app.tsx index 0c72f5d0..c3945edc 100644 --- a/apps/agent/src/pages/_app.tsx +++ b/apps/agent/src/pages/_app.tsx @@ -6,6 +6,7 @@ import Layout from "@/Layout/layout"; import { createApolloClient } from "@sahil/lib/graphql"; import Inter from "next/font/local"; import Plus_Jakarta_Sans from "next/font/local"; +import '../styles/nprogress.css' const inter = Inter({ src: "../../public/fonts/Inter-VariableFont_slnt,wght.ttf", diff --git a/apps/agent/src/pages/businesses/index.tsx b/apps/agent/src/pages/businesses/index.tsx index d5cac002..ca39ca60 100644 --- a/apps/agent/src/pages/businesses/index.tsx +++ b/apps/agent/src/pages/businesses/index.tsx @@ -1,7 +1,7 @@ import { ListBusinesses } from "@sahil/features/businesses/ListBusinesses"; import { HiPlus } from "react-icons/hi2"; import { SectionHeader } from "ui"; - +import { useUser } from "@sahil/features/auth/UserContext"; const actions = [ { label: "Register Business", @@ -12,9 +12,10 @@ const actions = [ ]; export default function Business() { + const { currentUser } = useUser(); return ( - +
- +
); } diff --git a/apps/agent/src/pages/couriers/index.tsx b/apps/agent/src/pages/couriers/index.tsx index e70555bd..6ddb5fb6 100644 --- a/apps/agent/src/pages/couriers/index.tsx +++ b/apps/agent/src/pages/couriers/index.tsx @@ -1,6 +1,8 @@ import { ListCouriers } from "@sahil/features/Couriers/ListCouriers"; import { HiOutlinePlus, HiOutlineMap } from "react-icons/hi2"; import { SectionHeader } from "ui"; +import { useUser } from "@sahil/features/auth/UserContext"; + const actions = [ { @@ -17,9 +19,12 @@ const actions = [ ]; export default function CouriersPage() { + const { currentUser } = useUser(); return ( - - - +
+ + + +
); } diff --git a/apps/agent/src/pages/notifications/index.tsx b/apps/agent/src/pages/notifications/index.tsx new file mode 100644 index 00000000..03359b0b --- /dev/null +++ b/apps/agent/src/pages/notifications/index.tsx @@ -0,0 +1,9 @@ +import { ListNotifications } from "@sahil/features/Notifications/ListNotifications"; + +export default function Notifications() { + return ( +
+ +
+ ); +} diff --git a/apps/agent/src/pages/orders/[orderId].tsx b/apps/agent/src/pages/orders/[orderId].tsx index 352d410d..9d6c7e2e 100644 --- a/apps/agent/src/pages/orders/[orderId].tsx +++ b/apps/agent/src/pages/orders/[orderId].tsx @@ -76,7 +76,7 @@ export default function OrderPage() {
- +
)} @@ -84,12 +84,14 @@ export default function OrderPage() { {currentTab === "progress" && (
+
)}
+
diff --git a/apps/agent/src/pages/orders/index.tsx b/apps/agent/src/pages/orders/index.tsx index dc3354bf..8a8ecf23 100644 --- a/apps/agent/src/pages/orders/index.tsx +++ b/apps/agent/src/pages/orders/index.tsx @@ -1,6 +1,28 @@ +import { useState } from 'react'; import { HiPlus } from "react-icons/hi2"; import { ListOrders } from "@sahil/features/Orders/ListOrders"; import { SectionHeader } from "ui"; +import OrderHeader from "@sahil/features/Orders/OrderHeader"; +import StatsPanel from "@sahil/features/Orders/StatsPanel"; + +type DateRange = 'today' | 'week' | 'month' | 'custom'; +type SortOption = 'date_desc' | 'date_asc' | 'status' | 'customer'; +type OrderStatus = 'CONFIRMED' | 'CANCELLED' | 'PENDING' | 'PROCESSING' | 'DELIVERED'; + +interface FilterState { + search: string; + status: OrderStatus | ''; + dateRange: DateRange; + customDateRange?: { from: Date; to: Date }; + sortBy: SortOption; +} + +const initialFilters: FilterState = { + search: '', + status: '', + dateRange: 'week', + sortBy: 'date_desc' +}; const actions = [ { @@ -12,9 +34,41 @@ const actions = [ ]; export default function OrdersPage() { + const [filters, setFilters] = useState(initialFilters); + const [activeBusiness, setActiveBusiness] = useState(null); + + // Dummy data for demonstration + const businesses = [ + { id: '1', name: 'Business A', totalOrders: 450 }, + { id: '2', name: 'Business B', totalOrders: 280 }, + { id: '3', name: 'Business C', totalOrders: 150 }, + ]; + + const stats = { + totalOrders: 1234, + pendingOrders: 56, + completedOrders: 1178 + }; + return ( - - - +
+ + +
+ +
+ + + +
+ +
+
); } diff --git a/apps/agent/src/pages/settings/billing.tsx b/apps/agent/src/pages/settings/billing.tsx new file mode 100644 index 00000000..0e721625 --- /dev/null +++ b/apps/agent/src/pages/settings/billing.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from "react"; +import { + SettingsSection, + Sidebar, + settingslinks, + SettingsContainer +} from "ui"; +import { BillingCard } from "@sahil/features/Settings/BillingCard"; + +type SidebarProps = { + children: ReactNode; +}; + +export default function BillingSettings({ children, ...props }: SidebarProps) { + return ( + + +
+ + + +
+
+ ); +} diff --git a/apps/agent/src/pages/settings/general.tsx b/apps/agent/src/pages/settings/general.tsx new file mode 100644 index 00000000..a485c198 --- /dev/null +++ b/apps/agent/src/pages/settings/general.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from "react"; +import { + SettingsSection, + Sidebar, + settingslinks, + SettingsContainer +} from "ui"; +import { GeneralCard } from "@sahil/features/Settings/GeneralCard"; + +type SidebarProps = { + children: ReactNode; +}; + +export default function GeneralSettings({ children, ...props }: SidebarProps) { + return ( + + +
+ + + +
+
+ ); +} diff --git a/apps/agent/src/pages/settings/index.tsx b/apps/agent/src/pages/settings/index.tsx deleted file mode 100644 index 6218dd3d..00000000 --- a/apps/agent/src/pages/settings/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { useGetUsers } from "@/hooks/users"; - -export default function Settings() { - return ( -
-

Settings Page!

-
- ); -} diff --git a/apps/agent/src/pages/settings/notifications.tsx b/apps/agent/src/pages/settings/notifications.tsx new file mode 100644 index 00000000..df7de6c4 --- /dev/null +++ b/apps/agent/src/pages/settings/notifications.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from "react"; +import { + SettingsSection, + Sidebar, + settingslinks, + SettingsContainer +} from "ui"; +import { NotificationsCard } from "@sahil/features/Settings/NotificationsCard"; + +type SidebarProps = { + children: ReactNode; +}; + +export default function NotificationsSettings({ children, ...props }: SidebarProps) { + return ( + + +
+ + + +
+
+ ); +} diff --git a/apps/agent/src/pages/settings/profile.tsx b/apps/agent/src/pages/settings/profile.tsx new file mode 100644 index 00000000..cf5724eb --- /dev/null +++ b/apps/agent/src/pages/settings/profile.tsx @@ -0,0 +1,33 @@ +import { ReactNode } from "react"; +import { + SettingsSection, + Sidebar, + settingslinks, + SettingsContainer +} from "ui"; +import { ProfileCard } from "@sahil/features/Settings/ProfileCard"; +import { DeleteAccountCard } from "@sahil/features/Settings/DeleteAccountCard"; + +type SidebarProps = { + children: ReactNode; +}; + +export default function ProfileSettings({ children, ...props }: SidebarProps) { + + return ( + + +
+ + + + + + + +
+
+ ); +} diff --git a/apps/agent/src/pages/settings/security.tsx b/apps/agent/src/pages/settings/security.tsx new file mode 100644 index 00000000..51e41c79 --- /dev/null +++ b/apps/agent/src/pages/settings/security.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from "react"; +import { + SettingsSection, + Sidebar, + settingslinks, + SettingsContainer +} from "ui"; +import { SecurityCard } from "@sahil/features/Settings/SecurityCard"; + +type SidebarProps = { + children: ReactNode; +}; + +export default function SecuritySettings({ children, ...props }: SidebarProps) { + return ( + + +
+ + + +
+
+ ); +} diff --git a/apps/agent/src/pages/suppliers/index.tsx b/apps/agent/src/pages/suppliers/index.tsx index 75f2fef8..2fdb1910 100644 --- a/apps/agent/src/pages/suppliers/index.tsx +++ b/apps/agent/src/pages/suppliers/index.tsx @@ -2,7 +2,8 @@ import { ListSuppliers } from "@sahil/features/Suppliers"; import FilterSuppliersModal from "@sahil/features/Suppliers/FilterSuppliersModal"; import { HiPlus } from "react-icons/hi2"; import { SectionHeader } from "ui"; - +import { CollectionControls } from "@sahil/features/Shared/CollectionControls"; +import { useUser } from "@sahil/features/auth/UserContext"; const actions = [ { label: "Register Supplier", @@ -13,8 +14,10 @@ const actions = [ ]; export default function Suppliers() { + const { currentUser } = useUser(); return ( + ); diff --git a/apps/agent/src/styles/nprogress.css b/apps/agent/src/styles/nprogress.css new file mode 100644 index 00000000..01035663 --- /dev/null +++ b/apps/agent/src/styles/nprogress.css @@ -0,0 +1,8 @@ +#nprogress .bar { + background: #067a46 !important; + height: 4px !important; +} + +#nprogress .peg { + box-shadow: 0 0 10px #067a46, 0 0 5px #056835 !important; +} \ No newline at end of file diff --git a/apps/client/package.json b/apps/client/package.json index 75b5b4b8..2a19ccc8 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -32,10 +32,12 @@ "next-auth-hasura-adapter": "^2.0.0", "postcss": "8.4.28", "react": "^18.3.1", + "react-day-picker": "8.10.0", "react-dom": "^18.3.1", "react-hook-form": "^7.45.4", "react-hot-toast": "^2.4.1", "react-icons": "^4.12.0", + "recharts": "^2.15.0", "subscriptions-transport-ws": "^0.11.0", "swr": "^2.2.1", "tailwindcss": "3.3.3", diff --git a/apps/client/src/Layout/layout.tsx b/apps/client/src/Layout/layout.tsx index 93cc60a3..5b9b59de 100644 --- a/apps/client/src/Layout/layout.tsx +++ b/apps/client/src/Layout/layout.tsx @@ -1,23 +1,41 @@ -import React, { ReactChild } from "react"; +import React, { ReactNode, useEffect } from "react"; +import logo from "../../public/logo-alt.svg"; import { useRouter } from "next/router"; import { signOut, useSession } from "next-auth/react"; -import { Navbar } from "ui"; -import logo from "../../public/logo-alt.svg"; -type LayoutProps = { - children: ReactChild | ReactChild[]; -}; + +import NProgress from "nprogress"; +import "nprogress/nprogress.css"; +import { UserProvider } from '@sahil/features/auth/UserContext'; +import { useGetUserById } from "@sahil/lib/hooks/users"; +import { SplashScreen, ContentLayout, Navbar } from "ui"; + import { - HiOutlineBriefcase, + HiOutlineDocumentChartBar, HiOutlineUserCircle, + HiOutlineShoppingCart, + HiOutlineBriefcase, + HiOutlineCube, HiOutlineTruck, - HiOutlineBuildingOffice, + HiOutlineBuildingStorefront, + HiMiniArrowLeftCircle, HiArrowPath } from "react-icons/hi2"; +import { Button } from "ui"; + +type LayoutProps = { + children: ReactNode; +}; + const links = [ { name: "Products", href: "/products", - icon: HiOutlineBriefcase, + icon: HiOutlineShoppingCart, + }, + { + name: "Suppliers", + href: "/suppliers", + icon: HiOutlineBuildingStorefront, }, { name: "Orders", @@ -29,29 +47,88 @@ const links = [ href: "/account", icon: HiOutlineUserCircle, }, + { + name: "Inventory", + href: "/inventory", + icon: HiOutlineCube + }, + { + name: "Reports", + href: "/reports", + icon: HiOutlineDocumentChartBar + } ]; export default function Layout({ children, ...props }: LayoutProps) { const router = useRouter(); const { data: session } = useSession(); + const isAuthRoute = router.pathname.startsWith('/auth'); + + const { data: currentUser, loading: userLoading } = useGetUserById(session?.user?.id); + useEffect(() => { + NProgress.configure({ + showSpinner: false, + trickleSpeed: 200, + minimum: 0.08 + }); + + const handleStart = () => { + NProgress.start(); + }; + + const handleStop = () => { + NProgress.done(); + }; + + router.events.on('routeChangeStart', handleStart); + router.events.on('routeChangeComplete', handleStop); + router.events.on('routeChangeError', handleStop); + + return () => { + router.events.off('routeChangeStart', handleStart); + router.events.off('routeChangeComplete', handleStop); + router.events.off('routeChangeError', handleStop); + }; + }, [router]); const onSignOut = async () => { - await signOut({ redirect: false, callbackUrl: "/signin" }); + await signOut(); router.push("/auth/signin"); }; + const handleBack = () => { + router.back(); + }; + + const handleRefresh = () => { + router.replace(router.asPath); + }; + + + if (!session && !isAuthRoute) { + return ; + } return ( - <> - {session?.user && ( - - )} -
{children}
- + + + + {children} + + ); } diff --git a/apps/client/src/pages/account/index.tsx b/apps/client/src/pages/account/index.tsx index bc629dec..7e7aa8cd 100644 --- a/apps/client/src/pages/account/index.tsx +++ b/apps/client/src/pages/account/index.tsx @@ -1,182 +1,87 @@ -import { - BusinessProfileOverview, - BusinessOrderHistory, -} from "@sahil/features/businesses"; -// import { useGetAccountBalance, useGetMomoAccountInfo } from "@/hooks/accounts"; -import { useFetchBusinessByPK } from "@sahil/lib/hooks/businesses"; -import { Card, JoinGrid } from "ui"; -import { useState } from "react"; -import { +"use client" + +import { useState } from "react" +import { Card, JoinGrid } from "ui" +import { + HiOutlineCurrencyDollar, + HiOutlineCreditCard, + HiOutlineDocumentText, + HiPlus, HiArrowSmallLeft, HiArrowSmallRight, HiOutlineMinusCircle, HiOutlineXCircle, - HiOutlineCheckCircle, -} from "react-icons/hi2"; -import { formatDateTime } from "@sahil/lib/dates"; -import { formatCurrency } from "@sahil/lib"; - -export default function Account() { - const { - data: business, - error, - loading, - } = useFetchBusinessByPK("e87924e8-69e4-4171-bd89-0c8963e03d08"); - - if (error) { - return

An error occurred while fetching you account details!

; - } + HiOutlineCheckCircle +} from "react-icons/hi2" +import { formatDateTime } from "@sahil/lib/dates" +import { formatCurrency } from "@sahil/lib" +import { BusinessProfileOverview } from "@sahil/features/businesses" +import { useFetchBusinessByPK } from "@sahil/lib/hooks/businesses" - return ( -
-
-
- { - // @ts-ignore - business && - } - -
-
- - -
+const PaymentMethodCard = ({ method, isPreferred, onEdit, onSetPreferred }) => ( + +
+
+ {method.type} ending in {method.last4} + {isPreferred && Preferred} +
+
+ + {!isPreferred && ( + + )}
- ); -} +
+) -const MomoAccountDetails = () => { - return ( - -

Momo Account Details

- - -
- ); -}; - -const MomoUserInfo = () => { - // const { data, loading, refetch } = useGetMomoAccountInfo(); - // const [isRefetching, setIsRefetching] = useState(false); +const TransactionCard = ({ transaction }) => { + const cardIcon = (status: string) => { + switch (status) { + case "Pending": + return + case "Canceled": + return + case "Confirmed": + return + default: + return null + } + } - // const handleRefetch = async () => { - // if (!isRefetching) { - // setIsRefetching(true); - // await refetch().then(() => setIsRefetching(false)); - // } - // }; return ( -
-
-

Given Name

- {/* {loading ? ( -
- ) : ( -

{data?.given_name}

- )} */} -
-
-

Family Name

- {/* {loading ? ( -
- ) : ( -

{data?.family_name}

- )} */} -
-
-

Gender

- {/* {loading ? ( -
- ) : ( -

{data?.gender}

- )} */} +
+
+ {cardIcon(transaction.status)}
-
-

Status

- {/* {loading ? ( -
- ) : ( -

{data?.status || "Active"}

- )} */} +
+ +
+
+

Method

+

{transaction.method}

+
+
+

Amount

+

{formatCurrency(transaction.amount)}

+
+
- {/* {data === null && ( -
-

- {isRefetching - ? "Refetching account info..." - : "Couldn't fetch account info."} -

- -
- )} */} - ); -}; - -const MomoAccountBalance = () => { - // const { data, loading, refetch } = useGetAccountBalance(); - const [isRefetching, setIsRefetching] = useState(false); - - // const handleRefetch = async () => { - // if (!isRefetching) { - // setIsRefetching(true); - // await refetch().then(() => setIsRefetching(false)); - // } - // }; + ) +} - return ( - -

Hello

- {/*
-
-

Currency

- {loading ? ( -
- ) : ( -

{data?.currency}

- )} -
-
-

Balance

- {loading ? ( -
- ) : ( -

{data?.availableBalance}

- )} -
-
- {data === null && ( -
-

- {isRefetching - ? "Refetching balance..." - : "Couldn't fetch account balance."} -

- -
- )} */} - - ); -}; +export default function BillingDashboard() { + const [paymentMethods, setPaymentMethods] = useState([ + { id: 1, type: 'Visa', last4: '1234' }, + { id: 2, type: 'Mastercard', last4: '5678' }, + ]) + const [preferredMethodId, setPreferredMethodId] = useState(1) -const TransactionsHistory = () => { const transactions = [ { amount: 1000, @@ -202,89 +107,114 @@ const TransactionsHistory = () => { status: "Confirmed", method: "Cash", }, - ]; + ] + + const handleAddPaymentMethod = () => { + // Implement add payment method logic + } + + const handleEditPaymentMethod = (id) => { + // Implement edit payment method logic + } + + const handleSetPreferred = (id) => { + setPreferredMethodId(id) + } + + const { + data: business, + error, + loading, + } = useFetchBusinessByPK("e87924e8-69e4-4171-bd89-0c8963e03d08") + + if (error) { + return

An error occurred while fetching your account details!

+ } + + if (loading) { + return

Loading...

+ } + return ( -
-
-

Latest Transactions

-
-
-
-
4 Transactions
-
-
- - +
+ +
+
+ {business && } + +
+

+ Payment Methods +

+ {paymentMethods.map((method) => ( + handleEditPaymentMethod(method.id)} + onSetPreferred={() => handleSetPreferred(method.id)} + /> + ))} - -
-
-
- {transactions.map((item, index) => ( - - ))} -
-
- ); -}; + -type cardProps = { - transaction: { - amount: number; - date: Date; - status: string; - method: string; - }; -}; - -const TransactionCard = ({ transaction }: cardProps) => { - const cardIcon = (status: string) => { - switch (status) { - case "Pending": - return ; - break; - case "Canceled": - return ; - break; - case "Confirmed": - return ; - } - }; - - return ( - -
-
- {cardIcon(transaction.status)} +
+

+ Summary +

+ +
+
+ Total Earned + $1,234.56 +
+
+ Total Spent + $567.89 +
+
+
+
-
- -
-
-

Method

-

{transaction.method}

+ +
+
+
+

+ Latest Transactions +

-
-

Amount

-

{formatCurrency(transaction.amount)}

+ +
+
+
{transactions.length} Transactions
+
+
+ + + + +
+
+ +
+ {transactions.map((transaction, index) => ( + + ))}
- - ); -}; +
+ ) +} + diff --git a/apps/client/src/pages/billing/index.tsx b/apps/client/src/pages/billing/index.tsx new file mode 100644 index 00000000..4ecf82af --- /dev/null +++ b/apps/client/src/pages/billing/index.tsx @@ -0,0 +1,102 @@ +import { HiOutlineCurrencyDollar, HiOutlineCreditCard, HiOutlineDocumentText, HiPlus } from "react-icons/hi2"; +import { Card } from "ui"; +import { useState } from "react"; + +const PaymentMethodCard = ({ method, isPreferred, onEdit, onSetPreferred }) => ( + +
+
+ {method.type} ending in {method.last4} + {isPreferred && Preferred} +
+
+ + {!isPreferred && ( + + )} +
+
+
+); + +export default function Billing() { + const [paymentMethods, setPaymentMethods] = useState([ + { id: 1, type: 'Visa', last4: '1234' }, + { id: 2, type: 'Mastercard', last4: '5678' }, + ]); + const [preferredMethodId, setPreferredMethodId] = useState(1); + + const handleAddPaymentMethod = () => { + // Implement add payment method logic + }; + + const handleEditPaymentMethod = (id) => { + // Implement edit payment method logic + }; + + const handleSetPreferred = (id) => { + setPreferredMethodId(id); + }; + + return ( +
+

Billing

+
+
+

+ Payment Methods +

+ {paymentMethods.map((method) => ( + handleEditPaymentMethod(method.id)} + onSetPreferred={() => handleSetPreferred(method.id)} + /> + ))} + +
+
+
+
+

+ Summary +

+
+
+ Total Earned + $1,234.56 +
+
+ Total Spent + $567.89 +
+
+
+
+

+ Recent Transactions +

+
    +
  • + Product Sale + +$50.00 +
  • +
  • + Platform Fee + -$5.00 +
  • +
+
+
+
+
+
+ ); +} diff --git a/apps/client/src/pages/index.tsx b/apps/client/src/pages/index.tsx index 75ae6b8d..14a7ea67 100644 --- a/apps/client/src/pages/index.tsx +++ b/apps/client/src/pages/index.tsx @@ -58,7 +58,7 @@ export default function Home() { Radisson Blu - + ); } diff --git a/apps/client/src/pages/inventory/[productId].tsx b/apps/client/src/pages/inventory/[productId].tsx new file mode 100644 index 00000000..b83cc0a7 --- /dev/null +++ b/apps/client/src/pages/inventory/[productId].tsx @@ -0,0 +1,258 @@ +import { useRouter } from "next/router"; +import React, { useState } from "react"; +import { + HiChevronLeft, + HiChevronRight, + HiOutlineInformationCircle, + HiOutlineShoppingCart, + HiShieldCheck, + HiHeart, + HiFlag, + HiArrowPath, +} from "react-icons/hi2"; +import { Card, Button, Tabs } from "ui"; +import { useSyncQueryWithStore } from "@sahil/lib/hooks/utilities/useQueryStore"; +import type { TabValue as BaseTabValue } from "@sahil/lib/hooks/utilities/useQueryStore"; +import { useFetchProductById } from "@sahil/lib/hooks/products"; +import ProductControls from "@sahil/features/Inventory/ProductControls"; + +export type TabValue = BaseTabValue; + +type TabItem = { + icon?: React.ReactNode; + label: string; + value: TabValue; +}; + +type Order = { + id: string; + orderId: string; + quantity: number; + type: string; +}; + +const ProductTabs: TabItem[] = [ + { + label: "Product Details", + value: "info", + icon: , + }, + { + label: "Orders Management", + value: "progress", + icon: , + }, +]; + +const ProductDetailsPage = ({ isSellerView = false }) => { + const router = useRouter(); + console.log(router.query); + const { productId } = router.query; + const { product, loading, error } = useFetchProductById(productId as string); + const { currentTab, handleChange } = useSyncQueryWithStore(); + const [currentImageIndex, setCurrentImageIndex] = useState(0); + + // Mock pending orders data + const pendingOrders: Order[] = [ + { + id: "1", + orderId: "ORD-001", + quantity: 2, + type: "Standard Delivery" + }, + { + id: "2", + orderId: "ORD-002", + quantity: 1, + type: "Express Delivery" + } + ]; + + console.log(product); + + const handleEdit = () => { + // Implement edit functionality + console.log("Edit product"); + }; + + const handleDelete = () => { + // Implement delete functionality + console.log("Delete product"); + }; + + if (loading) return
Loading...
; + if (error) return
Error loading product
; + if (!product) return
Product not found
; + + // Array for the images on the left side + const leftSideImages = [ + "https://i5.walmartimages.com/seo/Pre-Owned-Apple-iPhone-11-Pro-Fully-Unlocked-512GB-Midnight-Green-Certified-Used_3ecb3059-d3a6-4f43-bef3-a3eddb55fd6b_1.c7ce9c788df7e834ed0ceadfdb16c005.jpeg", + "https://buy.gazelle.com/cdn/shop/files/iPhone_11_Pro_Max_-_Black_-_Overlap_Trans-cropped_9f5a9ab5-8f9c-4d08-99f2-643f9c7caf24.jpg?v=1721951956", + ]; + + const nextImage = () => { + setCurrentImageIndex((prevIndex) => (prevIndex + 1) % leftSideImages.length); + }; + + const prevImage = () => { + setCurrentImageIndex( + (prevIndex) => (prevIndex - 1 + leftSideImages.length) % leftSideImages.length + ); + }; + + const handleTabClick = (value: TabValue) => { + handleChange(value); + }; + + return ( +
+ + +
+
+
+ {currentTab === "info" && ( +
+ +
+ {/* Image Container with Grid */} +
+ {/* Thumbnail Grid on Left */} +
+ {leftSideImages.map((image, index) => ( + + ))} +
+ + {/* Main Image */} +
+ {product.name} +
+
+
+ + + {/* Rest of the content */} +
+ )} + + {currentTab === "progress" && ( +
+
+

Pending Orders

+

+ Manage incoming orders for this product +

+
+ +
+ {pendingOrders.map((order) => ( + +
+
+
+

Order #{order.id}

+
Pending
+
+

+ Customer ID: {order.orderId} +

+
+

+ Quantity: {order.quantity} +

+

+ + {order.type} +

+
+
+ +
+
+ ))} +
+
+ )} +
+
+ +
+ +
+
+
+

+ {product.name} +

+

+ Added on {new Date(product.created_at).toLocaleDateString()} +

+
+ +
+

+ USh {product.price.toLocaleString()} +

+ {product.discount > 0 && ( +
+ {product.discount}% off +
+ )} +
+ {product.inStock ? "In Stock" : "Out of Stock"} +
+
+ +
+

Quantity Available

+

{product.quantity}

+
+ +
+

Description

+

{product.description}

+
+ + {!isSellerView && ( +
+ + +
+ )} +
+
+
+
+
+
+ ); +}; + +export default ProductDetailsPage; diff --git a/apps/client/src/pages/inventory/[productId]/edit.tsx b/apps/client/src/pages/inventory/[productId]/edit.tsx new file mode 100644 index 00000000..cf99e152 --- /dev/null +++ b/apps/client/src/pages/inventory/[productId]/edit.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { useRouter } from 'next/router'; +import { Card } from 'ui'; +import ProductForm, { ProductFormData } from '@sahil/features/Inventory/ProductForm'; +import { useMutation } from '@apollo/client'; +import { UPDATE_PRODUCT } from '@sahil/lib/graphql/mutations/products'; +import { useFetchProductById } from '@sahil/lib/hooks/products'; +import { HiShieldCheck, HiFlag } from 'react-icons/hi2'; + +const EditProductPage = () => { + const router = useRouter(); + const { productId } = router.query; + const { product, loading: fetchLoading } = useFetchProductById(productId as string); + const [updateProduct, { loading: updateLoading }] = useMutation(UPDATE_PRODUCT); + + const handleSubmit = async (data: ProductFormData) => { + try { + const result = await updateProduct({ + variables: { + id: productId, + input: data, + }, + }); + + if (result.data?.update_products_by_pk) { + router.push('/inventory'); + } + } catch (error) { + console.error('Error updating product:', error); + } + }; + + if (fetchLoading) return
Loading...
; + if (!product) return
Product not found
; + + return ( +
+
+
+ +
+

Edit Product

+

Update your product information

+
+ +
+
+ +
+ +
+
+ +

Product Guidelines

+
+
    +
  • • Use high-quality images for better visibility
  • +
  • • Provide accurate and detailed descriptions
  • +
  • • Keep pricing information up to date
  • +
  • • Regularly update stock availability
  • +
  • • Include all relevant product specifications
  • +
+
+
+ + +
+
+ +

Need Help?

+
+

+ {`If you're having trouble updating your product or have any questions, + our support team is here to help.`} +

+ +
+
+ + {product.supplier && ( + +
+

Supplier Information

+
+

+ Company: {product.supplier.name} +

+

+ Contact: {product.supplier.contactName} +

+

+ Email: {product.supplier.contactEmail} +

+

+ Phone: {product.supplier.phoneNumber} +

+
+
+
+ )} +
+
+
+ ); +}; + +export default EditProductPage; \ No newline at end of file diff --git a/apps/client/src/pages/inventory/index.tsx b/apps/client/src/pages/inventory/index.tsx new file mode 100644 index 00000000..975056d9 --- /dev/null +++ b/apps/client/src/pages/inventory/index.tsx @@ -0,0 +1,195 @@ +import { Card } from "ui"; +import { useSession } from "next-auth/react"; +import { HiOutlinePlus, HiOutlineArrowDown, HiArrowLeft, HiChevronRight } from "react-icons/hi2"; +import { useGetUserById } from "@sahil/lib/hooks/users"; +import { useUserSuppliers } from "@sahil/lib/hooks/useUserOrganizations"; +import { useFetchProducts } from "@sahil/lib/hooks/products"; +import type { Supplier } from "@sahil/lib/hooks/useUserOrganizations"; +import { useRouter } from 'next/router'; +import { formatDateTime } from "@sahil/lib/dates"; +import BusinessInventoryHeader from '@sahil/features/Inventory/BusinessInventoryHeader'; +import FilterPanel from '@sahil/features/Inventory/FilterPanel'; +import { CollectionControls } from "@sahil/features/Shared/CollectionControls"; + +interface ProductsTableProps { + products: any[]; + isLoading: boolean; + error?: Error; + onProductClick: (productId: string) => void; +} + +function ProductsTable({ products, isLoading, error, onProductClick }: ProductsTableProps) { + if (isLoading) { + return ( +
+
+ {[...Array(3)].map((_, i) => ( +
+ ))} +
+ ); + } + + if (error) { + return ( +
+ Error loading products +
+ ); + } + + if (!products?.length) { + return ( +
+ No products found +
+ ); + } + + return ( +
+ + + + + + + + + + + + + {products.map((product) => ( + onProductClick(product.id)} + > + + + + + + + + ))} + + + + + + + + + + +
+ + ProductSKUPriceStatus
e.stopPropagation()}> + + +
+
+
+ {product.name} +
+
+
+
{product.name}
+
{product.sku || 'No SKU'}
+
+
+
+ ${product.price} + + + {product.price} + + 0 ? 'badge-success' : 'badge-error' + } badge-sm`}> + {product.quantity > 0 ? 'In Stock' : 'Out of Stock'} + + e.stopPropagation()}> + +
ProductPrice & StockStatus
+
+ ); +} + +export default function InventoryPage() { + const router = useRouter(); + const { data: sessionData } = useSession(); + const { data: currentUser, loading: userLoading } = useGetUserById(sessionData?.user?.id); + + const { + suppliers, + activeSupplier, + switchSupplier, + loading: suppliersLoading + } = useUserSuppliers( + sessionData?.user?.id, + currentUser?.role + ); + + const { + data: products, + loading: productsLoading, + error: productsError + } = useFetchProducts({ + offset: 0, + limit: 12, + supplierId: activeSupplier?.id + }); + + const handleProductClick = (productId: string) => { + router.push(`/inventory/${productId}`); + }; + + const handleAddProduct = () => { + router.push('/inventory/new'); + }; + + if (userLoading || suppliersLoading) { + return
Loading...
; + } + + return ( +
+ + +
+ +
+ +
+
+
+ ); +} diff --git a/apps/client/src/pages/inventory/new.tsx b/apps/client/src/pages/inventory/new.tsx new file mode 100644 index 00000000..e4f2cf05 --- /dev/null +++ b/apps/client/src/pages/inventory/new.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { useRouter } from 'next/router'; +import { Card } from 'ui'; +import ProductForm, { ProductFormData } from '@sahil/features/Inventory/ProductForm'; +import { useMutation } from '@apollo/client'; +import { CREATE_PRODUCT } from '@sahil/lib/graphql/mutations/products'; +import { HiShieldCheck, HiFlag } from 'react-icons/hi2'; + +const NewProductPage = () => { + const router = useRouter(); + const [createProduct, { loading }] = useMutation(CREATE_PRODUCT); + + const handleSubmit = async (data: ProductFormData) => { + try { + const result = await createProduct({ + variables: { + input: data, + }, + }); + + if (result.data?.insert_products_one) { + router.push('/inventory'); + } + } catch (error) { + console.error('Error creating product:', error); + } + }; + + return ( +
+
+
+ +
+

Add New Product

+

Create a new product listing

+
+ +
+
+ +
+ +
+
+ +

Product Guidelines

+
+
    +
  • • Use high-quality images for better visibility
  • +
  • • Provide accurate and detailed descriptions
  • +
  • • Keep pricing information up to date
  • +
  • • Regularly update stock availability
  • +
  • • Include all relevant product specifications
  • +
+
+
+ + +
+
+ +

Need Help?

+
+

+ If you're having trouble adding a new product or have any questions, + our support team is here to help. +

+ +
+
+
+
+
+ ); +}; + +export default NewProductPage; \ No newline at end of file diff --git a/apps/client/src/pages/notifications/index.tsx b/apps/client/src/pages/notifications/index.tsx new file mode 100644 index 00000000..03359b0b --- /dev/null +++ b/apps/client/src/pages/notifications/index.tsx @@ -0,0 +1,9 @@ +import { ListNotifications } from "@sahil/features/Notifications/ListNotifications"; + +export default function Notifications() { + return ( +
+ +
+ ); +} diff --git a/apps/client/src/pages/orders/index.tsx b/apps/client/src/pages/orders/index.tsx index 89bace9b..e359f673 100644 --- a/apps/client/src/pages/orders/index.tsx +++ b/apps/client/src/pages/orders/index.tsx @@ -1,13 +1,75 @@ -// import { OrderHistory } from "@sahil/features/Orders/ListOrders"; +import { useState } from 'react'; +import { HiPlus } from "react-icons/hi2"; +import { ListOrders } from "@sahil/features/Orders/ListOrders"; +import { SectionHeader } from "ui"; +import OrderHeader from "@sahil/features/Orders/OrderHeader"; +import { useFetchSupplierOrders } from "@sahil/lib/hooks/suppliers"; +import { useGetUserById } from "@sahil/lib/hooks/users"; +import { useSession } from "next-auth/react"; +import { useRouter } from "next/router"; +import { useUserSuppliers } from "@sahil/lib/hooks/useUserOrganizations"; +import { CollectionControls } from "@sahil/features/Shared/CollectionControls"; + + +type DateRange = 'today' | 'week' | 'month' | 'custom'; +type SortOption = 'date_desc' | 'date_asc' | 'status' | 'customer'; +type OrderStatus = 'CONFIRMED' | 'CANCELLED' | 'PENDING' | 'PROCESSING' | 'DELIVERED'; + +interface FilterState { + search: string; + status: OrderStatus | ''; + dateRange: DateRange; + customDateRange?: { from: Date; to: Date }; + sortBy: SortOption; +} + +const initialFilters: FilterState = { + search: '', + status: '', + dateRange: 'week', + sortBy: 'date_desc' +}; + +const actions = [ + { + label: "New Order", + icon: , + href: "/orders/new/order_details", + primary: true, + }, +]; + +export default function OrdersPage() { + const router = useRouter(); + const { data: sessionData } = useSession(); + const { data: currentUser, loading: userLoading } = useGetUserById(sessionData?.user?.id); + + + const [filters, setFilters] = useState(initialFilters); + const [activeBusiness, setActiveBusiness] = useState(null); + + const { + suppliers, + activeSupplier, + switchSupplier, + loading: suppliersLoading + } = useUserSuppliers( + sessionData?.user?.id, + currentUser?.role + ); + + const stats = { + totalOrders: 1234, + pendingOrders: 56, + completedOrders: 1178 + }; -export default function Orders() { return ( -
-
-

- Orders Page -

-
-
+
+ + + + +
); } diff --git a/apps/client/src/pages/products/index.tsx b/apps/client/src/pages/products/index.tsx index a6f3d1be..c7424161 100644 --- a/apps/client/src/pages/products/index.tsx +++ b/apps/client/src/pages/products/index.tsx @@ -2,10 +2,15 @@ import { useState } from "react"; import { useRouter } from "next/router"; import { ProductsCatalogue } from "@sahil/features/Products/ProductsCatalogue"; import { HiMagnifyingGlass, HiOutlineShoppingCart } from "react-icons/hi2"; +import { CollectionControls } from "@sahil/features/Shared/CollectionControls"; +import { useSession } from "next-auth/react"; +import { useGetUserById } from "@sahil/lib/hooks/users"; export default function Products() { const [name, setName] = useState(""); const router = useRouter(); + const { data: sessionData } = useSession(); + const { data: currentUser, loading: userLoading } = useGetUserById(sessionData?.user?.id); const onInputChange = (e: { target: { value: string } }) => { const value = e.target.value; @@ -31,17 +36,7 @@ export default function Products() { return (
-
-

Available Products

-
-
- 5 - -
-
-
+
diff --git a/apps/client/src/pages/reports/index.tsx b/apps/client/src/pages/reports/index.tsx new file mode 100644 index 00000000..7940b67b --- /dev/null +++ b/apps/client/src/pages/reports/index.tsx @@ -0,0 +1,167 @@ +import { useFetchSupplierOrders } from "@sahil/lib/hooks/suppliers"; +import { useState } from "react"; +import { useGetUserById } from "@sahil/lib/hooks/users"; +import { useSession } from "next-auth/react"; +import { useUserSuppliers } from "@sahil/lib/hooks/useUserOrganizations"; + +import * as React from "react" + +import { CollectionControls } from "@sahil/features/Shared/CollectionControls"; +import { TopClientsByRevenue } from "@sahil/features/Reports/TopClientsByRevenue"; +import { RevenueByProduct } from "@sahil/features/Reports/RevenueByProduct"; +import { OrderStatusDistribution } from "@sahil/features/Reports/OrderStatusDistribution"; + + +const orders = [ + { + "business": { + "id": "e87924e8-69e4-4171-bd89-0c8963e03d08", + "name": "Radisson Blu", + "contactName": "Emmanuel Gatwech", + "business_type": { + "type": "hotel" + }, + "type": "hotel" + }, + "id": "d634372a-6a81-402e-9fa5-231bf7c0444c", + "fulfillment_type": null, + "order_items": [ + { + "price": 15, + "product": { + "name": "Routers", + "price": 10000, + "quantity": 20, + "discount": 0 + } + }, + { + "price": 15, + "product": { + "name": "Laptops", + "price": 1000000, + "quantity": 6, + "discount": 15 + } + }, + { + "price": 15, + "product": { + "name": "iPhone 11 Pro Max", + "price": 10000000, + "quantity": 3, + "discount": 1 + } + } + ], + "status": "PENDING", + "origin": "Souq Munuki", + "created_at": "2024-09-14T13:42:07.748051+00:00", + "destination": "Souq Custom" + }, + { + "business": { + "id": "e87924e8-69e4-4171-bd89-0c8963e03d08", + "name": "Radisson Blu", + "contactName": "Emmanuel Gatwech", + "business_type": { + "type": "hotel" + }, + "type": "hotel" + }, + "id": "c3ce2967-53fc-4fc6-922b-350adf4c773c", + "fulfillment_type": null, + "order_items": [ + { + "price": 15, + "product": { + "name": "1kg Sugar", + "price": 2500, + "quantity": 250, + "discount": null + } + }, + { + "price": 15, + "product": { + "name": "1kg Brazillian Chicken", + "price": 300, + "quantity": 25, + "discount": null + } + }, + { + "price": 15, + "product": { + "name": "1kg Powder Milk", + "price": 2500, + "quantity": 10, + "discount": null + } + } + ], + "status": "PENDING", + "origin": "Souq Munuki", + "created_at": "2024-09-16T18:21:09.961872+00:00", + "destination": "Souq Custom" + }, + { + "business": { + "id": "e87924e8-69e4-4171-bd89-0c8963e03d08", + "name": "Radisson Blu", + "contactName": "Emmanuel Gatwech", + "business_type": { + "type": "hotel" + }, + "type": "hotel" + }, + "id": "99738f85-6c6a-4c79-896d-bea5220108ef", + "fulfillment_type": null, + "order_items": [ + { + "price": 15, + "product": { + "name": "Solar Batteries", + "price": 350000, + "quantity": 10, + "discount": null + } + }, + { + "price": 15, + "product": { + "name": "250w Solar Panel", + "price": 300000, + "quantity": 20, + "discount": null + } + } + ], + "status": "PENDING", + "origin": "Souq Munuki", + "created_at": "2024-09-17T14:41:35.21956+00:00", + "destination": "Souq Custom" + } +]; + + + + +export default function Reports() { + const { data: sessionData } = useSession(); + const { data: currentUser, loading: userLoading } = useGetUserById(sessionData?.user?.id); + + const supplierOrders = useFetchSupplierOrders(); + console.log(supplierOrders); + + return ( +
+ +
+ + + +
+
+ ); +} diff --git a/apps/client/src/pages/settings/billing.tsx b/apps/client/src/pages/settings/billing.tsx new file mode 100644 index 00000000..0e721625 --- /dev/null +++ b/apps/client/src/pages/settings/billing.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from "react"; +import { + SettingsSection, + Sidebar, + settingslinks, + SettingsContainer +} from "ui"; +import { BillingCard } from "@sahil/features/Settings/BillingCard"; + +type SidebarProps = { + children: ReactNode; +}; + +export default function BillingSettings({ children, ...props }: SidebarProps) { + return ( + + +
+ + + +
+
+ ); +} diff --git a/apps/client/src/pages/settings/general.tsx b/apps/client/src/pages/settings/general.tsx new file mode 100644 index 00000000..a485c198 --- /dev/null +++ b/apps/client/src/pages/settings/general.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from "react"; +import { + SettingsSection, + Sidebar, + settingslinks, + SettingsContainer +} from "ui"; +import { GeneralCard } from "@sahil/features/Settings/GeneralCard"; + +type SidebarProps = { + children: ReactNode; +}; + +export default function GeneralSettings({ children, ...props }: SidebarProps) { + return ( + + +
+ + + +
+
+ ); +} diff --git a/apps/client/src/pages/settings/notifications.tsx b/apps/client/src/pages/settings/notifications.tsx new file mode 100644 index 00000000..df7de6c4 --- /dev/null +++ b/apps/client/src/pages/settings/notifications.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from "react"; +import { + SettingsSection, + Sidebar, + settingslinks, + SettingsContainer +} from "ui"; +import { NotificationsCard } from "@sahil/features/Settings/NotificationsCard"; + +type SidebarProps = { + children: ReactNode; +}; + +export default function NotificationsSettings({ children, ...props }: SidebarProps) { + return ( + + +
+ + + +
+
+ ); +} diff --git a/apps/client/src/pages/settings/profile.tsx b/apps/client/src/pages/settings/profile.tsx new file mode 100644 index 00000000..cf5724eb --- /dev/null +++ b/apps/client/src/pages/settings/profile.tsx @@ -0,0 +1,33 @@ +import { ReactNode } from "react"; +import { + SettingsSection, + Sidebar, + settingslinks, + SettingsContainer +} from "ui"; +import { ProfileCard } from "@sahil/features/Settings/ProfileCard"; +import { DeleteAccountCard } from "@sahil/features/Settings/DeleteAccountCard"; + +type SidebarProps = { + children: ReactNode; +}; + +export default function ProfileSettings({ children, ...props }: SidebarProps) { + + return ( + + +
+ + + + + + + +
+
+ ); +} diff --git a/apps/client/src/pages/settings/security.tsx b/apps/client/src/pages/settings/security.tsx new file mode 100644 index 00000000..51e41c79 --- /dev/null +++ b/apps/client/src/pages/settings/security.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from "react"; +import { + SettingsSection, + Sidebar, + settingslinks, + SettingsContainer +} from "ui"; +import { SecurityCard } from "@sahil/features/Settings/SecurityCard"; + +type SidebarProps = { + children: ReactNode; +}; + +export default function SecuritySettings({ children, ...props }: SidebarProps) { + return ( + + +
+ + + +
+
+ ); +} diff --git a/apps/client/src/pages/suppliers/[supplierId].tsx b/apps/client/src/pages/suppliers/[supplierId].tsx new file mode 100644 index 00000000..f8f18c88 --- /dev/null +++ b/apps/client/src/pages/suppliers/[supplierId].tsx @@ -0,0 +1,34 @@ +import { useRouter } from "next/router"; +import { useFetchSupplierByPK } from "@sahil/lib/hooks/suppliers"; +import { + SupplierOrderHistory, + SupplierProducts, + ServiceZones, + SupplierProfileOverview, +} from "@sahil/features/Suppliers"; +import { Suppliers } from "@sahil/lib/graphql/__generated__/graphql"; + +export default function SupplierPage() { + const router = useRouter(); + const { supplierId } = router.query; + const { data: supplier, error, loading } = useFetchSupplierByPK(); + if (error) { + return

Failed

; + } + return ( +
+
+
+ + +
+
+ + +
+
+
+ ); +} diff --git a/apps/client/src/pages/suppliers/index.tsx b/apps/client/src/pages/suppliers/index.tsx new file mode 100644 index 00000000..2fdb1910 --- /dev/null +++ b/apps/client/src/pages/suppliers/index.tsx @@ -0,0 +1,24 @@ +import { ListSuppliers } from "@sahil/features/Suppliers"; +import FilterSuppliersModal from "@sahil/features/Suppliers/FilterSuppliersModal"; +import { HiPlus } from "react-icons/hi2"; +import { SectionHeader } from "ui"; +import { CollectionControls } from "@sahil/features/Shared/CollectionControls"; +import { useUser } from "@sahil/features/auth/UserContext"; +const actions = [ + { + label: "Register Supplier", + icon: , + href: "/suppliers/register/business_info", + primary: true, + }, +]; + +export default function Suppliers() { + const { currentUser } = useUser(); + return ( + + + + + ); +} diff --git a/apps/client/src/pages/suppliers/products/index.tsx b/apps/client/src/pages/suppliers/products/index.tsx new file mode 100644 index 00000000..5af43bb0 --- /dev/null +++ b/apps/client/src/pages/suppliers/products/index.tsx @@ -0,0 +1,20 @@ +import { ProductsCatalogue } from "@sahil/features/Products/ProductsCatalogue"; + +export default function SupplierProductsPage() { + return ( +
+
+
+
+

Supplier Page

+

+ Users can register through agents, an app, or USSD codes and set + delivery preferences. +

+
+
+ +
+
+ ); +} diff --git a/apps/courier/package.json b/apps/courier/package.json index 36b533f8..76d0f89e 100644 --- a/apps/courier/package.json +++ b/apps/courier/package.json @@ -12,7 +12,7 @@ "@apollo/react-hooks": "^4.0.0", "@formkit/auto-animate": "^0.7.0", "@hookform/resolvers": "^3.3.0", - "@react-google-maps/api": "^2.19.2", + "@react-google-maps/api": "^2.20.3", "@sahil/configs": "*", "@sahil/features": "*", "@sahil/lib": "*", diff --git a/apps/courier/src/Layout/layout.tsx b/apps/courier/src/Layout/layout.tsx index d1c155fa..9aa7fda8 100644 --- a/apps/courier/src/Layout/layout.tsx +++ b/apps/courier/src/Layout/layout.tsx @@ -1,12 +1,28 @@ -import React, { ReactNode } from "react"; +import React, { ReactNode, useEffect } from "react"; import logo from "../../public/logo-alt.svg"; import { useRouter } from "next/router"; import { signOut, useSession } from "next-auth/react"; -import { Navbar } from "ui"; +import { ContentLayout, Navbar } from "ui"; +import NProgress from "nprogress"; +import "nprogress/nprogress.css"; +import { UserProvider } from '@sahil/features/auth/UserContext'; +import { useGetUserById } from "@sahil/lib/hooks/users"; +import { SplashScreen } from "ui"; + +import { + HiOutlineTruck, + HiOutlineQueueList, + HiOutlineUser, + HiOutlineBell, + HiOutlineMapPin, + HiOutlineHome +} from "react-icons/hi2"; + +import { Button } from "ui"; + type LayoutProps = { children: ReactNode; }; -import { HiOutlineQueueList, HiOutlineTruck } from "react-icons/hi2"; const links = [ { @@ -21,26 +37,99 @@ const links = [ }, ]; +const bottomLinks = [ + { + icon: HiOutlineHome, + name: 'Home', + href: '/' + }, + { + icon: HiOutlineMapPin, + name: 'Deliveries', + href: '/deliveries' + }, + { + href: "/requests", + name: "Requests", + icon: HiOutlineBell, + }, + { + icon: HiOutlineUser, + name: 'Profile', + href: '/profile' + } +] + export default function Layout({ children, ...props }: LayoutProps) { const router = useRouter(); const { data: session } = useSession(); + const isAuthRoute = router.pathname.startsWith('/auth'); + + const { data: currentUser, loading: userLoading } = useGetUserById(session?.user?.id); + useEffect(() => { + NProgress.configure({ + showSpinner: false, + trickleSpeed: 200, + minimum: 0.08 + }); + + const handleStart = () => { + NProgress.start(); + }; + + const handleStop = () => { + NProgress.done(); + }; + + router.events.on('routeChangeStart', handleStart); + router.events.on('routeChangeComplete', handleStop); + router.events.on('routeChangeError', handleStop); + + return () => { + router.events.off('routeChangeStart', handleStart); + router.events.off('routeChangeComplete', handleStop); + router.events.off('routeChangeError', handleStop); + }; + }, [router]); const onSignOut = async () => { await signOut(); router.push("/auth/signin"); }; + + const handleBack = () => { + router.back(); + }; + + const handleRefresh = () => { + router.replace(router.asPath); + }; + + + if (!session && !isAuthRoute) { + return ; + } return ( - <> - {session?.user && ( - - )} -
{children}
- + + + + {children} + + ); } diff --git a/apps/courier/src/pages/deliveries/[deliveryId].tsx b/apps/courier/src/pages/deliveries/[deliveryId].tsx index f8ddc8c9..f230fa92 100644 --- a/apps/courier/src/pages/deliveries/[deliveryId].tsx +++ b/apps/courier/src/pages/deliveries/[deliveryId].tsx @@ -1,84 +1,10 @@ import { useRouter } from "next/router"; import { useFetchDeliveryByPK } from "@sahil/lib/hooks/deliveries"; -import { Card } from "ui"; -import { OrderItem } from "@sahil/features/Orders/OrderItems"; -import { OrderInfoItem } from "@sahil/features/Orders/OrderDetails"; -import { formatDateTime } from "@sahil/lib/dates"; -import { - HiCalendarDays, - HiOutlineBanknotes, - HiOutlineBriefcase, - HiOutlineMap, - HiOutlineMapPin, - HiOutlineCheck, - HiOutlineTruck, -} from "react-icons/hi2"; +import { DeliveryDetails } from "@sahil/features/Deliveries/DeliveryDetails"; -const DeliveryOrders = ({ order, deliveryStatus, onUpdateStatus }) => { - console.log(order); - return ( -
- {order?.order_items.map((order) => ( - -

Order Details

-

Delivery Order ID: {order.id}

- {order?.order_items && order?.order_items?.length > 0 ? ( -
- {order.order_items.map((item, index) => ( - - ))} -
- ) : ( -

No items found for this order.

- )} - -
- ))} -
- ); -}; - -const DeliveryActions = ({ orderId, deliveryStatus, onUpdateStatus }) => { - return ( -
- - - -
- ); -}; - -// Stub for useUpdateDeliveryStatus hook const useUpdateDeliveryStatus = () => { const updateDeliveryStatus = async (orderId: string, newStatus: string) => { console.log(`Updating order ${orderId} to status ${newStatus}`); - // Implement actual update logic here when the hook is ready }; return { updateDeliveryStatus }; @@ -98,25 +24,7 @@ export default function DeliveryPage() { if (loading) return

Loading delivery information...

; if (!delivery) return

No delivery found

; - const deliveryInfoItems = [ - { - icon: , - label: "Payment Method", - value: "N/A", - }, - { - icon: , - label: "Client", - value: "N/A", - }, - { - icon: , - label: "Status", - value: "N/A", - }, - ]; - - const handleUpdateStatus = async (orderId, newStatus) => { + const handleUpdateStatus = async (orderId: string, newStatus: string) => { try { await updateDeliveryStatus(orderId, newStatus); // Optionally, you can refetch the delivery data here to update the UI @@ -125,49 +33,50 @@ export default function DeliveryPage() { } }; - console.log(delivery[0]?.order); + // Dummy data for demonstration + const dummyDeliveryData = { + id: delivery[0]?.id || "D123", + status: "pending" as const, // Type assertion to fix status type + client: { + name: "John Doe", + address: "123 Main St, City, Country", + phone: "+1 234 567 890" + }, + items: [ + { name: "Product 1", quantity: 2, price: 29.99 }, + { name: "Product 2", quantity: 1, price: 49.99 } + ], + payment: { + method: "Credit Card", + total: 109.97, + commission: 10.99 + }, + timing: { + startTime: "2024-03-15 14:30", + duration: "45 mins" + }, + distance: "3.2 km" + }; return ( -
- -
-
- {deliveryInfoItems.map((item, index) => ( - - ))} -
-
- - -
- {/* { - - delivery.status === 'pending' && ( - - )} */} +
+ + {dummyDeliveryData.status === 'pending' && ( +
+ +
- - + )}
); } diff --git a/apps/courier/src/pages/deliveries/index.tsx b/apps/courier/src/pages/deliveries/index.tsx index a51f4733..2d6f52ce 100644 --- a/apps/courier/src/pages/deliveries/index.tsx +++ b/apps/courier/src/pages/deliveries/index.tsx @@ -1,7 +1,24 @@ import { ListDeliveries } from "@sahil/features/Deliveries/ListDeliveries"; +import { Button, IconButton } from "ui"; +import { HiOutlineArrowRight, HiOutlineArrowLeft } from "react-icons/hi2"; + + export default function Deliveries() { return ( -
+
+
+ + +
+
+

Hello, James

+

+ Here are your delivery requests +

+
+
); diff --git a/apps/courier/src/pages/index.tsx b/apps/courier/src/pages/index.tsx index 9f15086e..c273249e 100644 --- a/apps/courier/src/pages/index.tsx +++ b/apps/courier/src/pages/index.tsx @@ -1,20 +1,10 @@ -import Image from "next/image"; -import { Inter } from "next/font/google"; -import { useFetchCouriers } from "@sahil/lib/hooks/couriers"; -import { LatestDeliveries } from "@sahil/features/Couriers/LatestDeliveries"; -import { IncomingDeliveryRequest } from "@sahil/features/Couriers/IncomingDeliveryRequest"; -import { PhoneNumberInput } from "@sahil/features/auth/PhoneNumberInput"; -import { VerificationCodeInput } from "@sahil/features/auth/VerificationCodeInput"; - -const request = { - id: 1, - name: "BBQ Pizza", -}; - +import { MapView } from "@sahil/features/Maps/MapView"; +import { TripInfo } from "@sahil/features/Maps/TripInfo"; export default function Home() { return (
- + +
); } diff --git a/apps/courier/src/pages/notifications/index.tsx b/apps/courier/src/pages/notifications/index.tsx new file mode 100644 index 00000000..03359b0b --- /dev/null +++ b/apps/courier/src/pages/notifications/index.tsx @@ -0,0 +1,9 @@ +import { ListNotifications } from "@sahil/features/Notifications/ListNotifications"; + +export default function Notifications() { + return ( +
+ +
+ ); +} diff --git a/apps/courier/src/pages/requests/[requestId].tsx b/apps/courier/src/pages/requests/[requestId].tsx index a38cb4d0..25ee20f0 100644 --- a/apps/courier/src/pages/requests/[requestId].tsx +++ b/apps/courier/src/pages/requests/[requestId].tsx @@ -82,7 +82,7 @@ export default function RequestPage() { const isSingleOrder = deliveryRequest[0].delivery_request_orders.length === 1; return ( -
+
diff --git a/apps/courier/src/pages/requests/index.tsx b/apps/courier/src/pages/requests/index.tsx index a5af0a62..1a2a1f0c 100644 --- a/apps/courier/src/pages/requests/index.tsx +++ b/apps/courier/src/pages/requests/index.tsx @@ -1,8 +1,24 @@ import { ListDeliveryRequests } from "@sahil/features/Deliveries/ListDeliveryRequests"; +import { Button } from "ui"; +import { HiOutlineArrowRight } from "react-icons/hi2"; export default function Requests() { return ( -
+
+
+
+

Hello, James

+

+ Here are your delivery requests +

+
+
+ +
+
); diff --git a/apps/website/public/about.png b/apps/website/public/about.png deleted file mode 100644 index 812fdae8..00000000 Binary files a/apps/website/public/about.png and /dev/null differ diff --git a/apps/website/public/agent-business.png b/apps/website/public/agent-business.png deleted file mode 100644 index 7220ac1c..00000000 Binary files a/apps/website/public/agent-business.png and /dev/null differ diff --git a/apps/website/public/agent-desktop.svg b/apps/website/public/agent-desktop.svg new file mode 100644 index 00000000..3f1a3a2a --- /dev/null +++ b/apps/website/public/agent-desktop.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/agent-mobile.svg b/apps/website/public/agent-mobile.svg new file mode 100644 index 00000000..85f6e54d --- /dev/null +++ b/apps/website/public/agent-mobile.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/agent-supplier.png b/apps/website/public/agent-supplier.png deleted file mode 100644 index d69b6609..00000000 Binary files a/apps/website/public/agent-supplier.png and /dev/null differ diff --git a/apps/website/public/agent.png b/apps/website/public/agent.png deleted file mode 100644 index ec468038..00000000 Binary files a/apps/website/public/agent.png and /dev/null differ diff --git a/apps/website/public/cta-yellow.svg b/apps/website/public/cta-yellow.svg deleted file mode 100644 index ff7bea90..00000000 --- a/apps/website/public/cta-yellow.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/website/public/cta.png b/apps/website/public/cta.png deleted file mode 100644 index b5dfe803..00000000 Binary files a/apps/website/public/cta.png and /dev/null differ diff --git a/apps/website/public/delivery.jpg b/apps/website/public/delivery.jpg new file mode 100644 index 00000000..16abceec Binary files /dev/null and b/apps/website/public/delivery.jpg differ diff --git a/apps/website/public/feature-1.svg b/apps/website/public/feature-1.svg new file mode 100644 index 00000000..6f52c3ea --- /dev/null +++ b/apps/website/public/feature-1.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/feature-2.svg b/apps/website/public/feature-2.svg new file mode 100644 index 00000000..b128fc02 --- /dev/null +++ b/apps/website/public/feature-2.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/feature-3.svg b/apps/website/public/feature-3.svg new file mode 100644 index 00000000..9a4942a8 --- /dev/null +++ b/apps/website/public/feature-3.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/hero.png b/apps/website/public/hero.png deleted file mode 100644 index 39dd4b8b..00000000 Binary files a/apps/website/public/hero.png and /dev/null differ diff --git a/apps/website/public/service-3.svg b/apps/website/public/service-3.svg deleted file mode 100644 index 6d319873..00000000 --- a/apps/website/public/service-3.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/website/src/app/about/page.tsx b/apps/website/src/app/about/page.tsx index 97317d82..ac9ac1e3 100644 --- a/apps/website/src/app/about/page.tsx +++ b/apps/website/src/app/about/page.tsx @@ -1,4 +1,6 @@ -import { ComingSoon, GridContainer, PageTitle } from "@/components/shared"; +import { Card, GridContainer, JourneySection, PageTitle, SectionWrapper } from "@/components/shared"; +import about from "../../../public/delivery.jpg"; +import { Cta } from "@/components/segments"; export const metadata = { title: "About Us - Sahil App", @@ -15,8 +17,52 @@ export default function AboutPage() { description="Get to know us better! Learn more about our mission, our vision, and the values that guide our work." /> - + + +
+

+ + We are a groundbreaking supply chain management platform that empowers small and medium-sized enterprises + (SMEs) in challenging business environments. + {''} + + Our platform is committed to providing a seamless + experience for all our users, ensuring they can focus on what matters most - growing their + business. + +

+
+ +
+ + +
+ +
+
+
+ ); } diff --git a/apps/website/src/app/contact/page.tsx b/apps/website/src/app/contact/page.tsx index 2d81b046..a22283ff 100644 --- a/apps/website/src/app/contact/page.tsx +++ b/apps/website/src/app/contact/page.tsx @@ -1,21 +1,81 @@ -import { ComingSoon, GridContainer, PageTitle } from "@/components/shared"; +import { + ContactCard, + ContactCardProps, + GridContainer, + PageTitle, +} from "@/components/shared"; +import { Cta, Faqs } from "@/components/segments"; +import { HiEnvelope, HiMapPin, HiPhone } from "react-icons/hi2"; export const metadata = { title: "Contact Us - Sahil App", description: "We’re here to help and would love to hear from you.", }; +const contactDetails: ContactCardProps[] = [ + { + icon: HiEnvelope, + title: "Drop us a line", + content: "sahil.business@gmail.com", + linkHref: "mailto:sahil.business@gmail.com", + colorScheme: "neutral" + }, + { + icon: HiMapPin, + title: "Our Head Office", + content: ( + <> + Norrsken House Kigali
+ 1 KN 78 St, Kigali - Rwanda + + ), + colorScheme: "gray" + }, + { + icon: HiPhone, + title: "Book a Call", + content: "+250-790-336-525", + linkHref: "tel:+250790336525", + colorScheme: "slate" + } +]; + export default function ContactPage() { return ( <> - +
+
+
+

+ Reach Us +

+

+ If you have any questions or need assistance, feel free to reach out to us through our contact details below. +

+
+ {contactDetails.map((contact, index) => ( + + ))} +
+
+
+
+ + ); } diff --git a/apps/website/src/app/features/page.tsx b/apps/website/src/app/features/page.tsx index dbde354a..cdc3ded3 100644 --- a/apps/website/src/app/features/page.tsx +++ b/apps/website/src/app/features/page.tsx @@ -1,4 +1,5 @@ -import { PageTitle, GridContainer, ComingSoon } from "@/components/shared"; +import { Benefits, Cta, Features } from "@/components/segments"; +import { PageTitle } from "@/components/shared"; export const metadata = { title: "Features - Sahil App", @@ -14,9 +15,9 @@ export default function FeaturesPage() { subtitle="Our Services" description="Explore the wide range of features we offer to help you revolutionize your business operations." /> - - - + + + ); } diff --git a/apps/website/src/app/layout.tsx b/apps/website/src/app/layout.tsx index 1b239f00..50a36700 100644 --- a/apps/website/src/app/layout.tsx +++ b/apps/website/src/app/layout.tsx @@ -1,12 +1,11 @@ import "../styles/globals.css"; import type { Metadata } from "next"; -import { Alata, Plus_Jakarta_Sans } from "next/font/google"; +import { Inter, Plus_Jakarta_Sans } from "next/font/google"; import { Navbar, Footer } from "@/components/layout"; -const alata = Alata({ - weight: "400", +const inter = Inter({ subsets: ["latin"], - variable: "--font-alata", + variable: "--font-inter", display: "swap", }); @@ -29,7 +28,7 @@ export default function RootLayout({ return ( {children} diff --git a/apps/website/src/app/page.tsx b/apps/website/src/app/page.tsx index b67fe4bc..558516ec 100644 --- a/apps/website/src/app/page.tsx +++ b/apps/website/src/app/page.tsx @@ -3,9 +3,9 @@ import { Features, About, Benefits, - RegistrationRole, Cta, Faqs, + ParterWithUs, } from "../components/segments"; export default function Page() { @@ -15,7 +15,7 @@ export default function Page() { - + diff --git a/apps/website/src/app/partners/businesses/page.tsx b/apps/website/src/app/partners/businesses/page.tsx new file mode 100644 index 00000000..c19f7425 --- /dev/null +++ b/apps/website/src/app/partners/businesses/page.tsx @@ -0,0 +1,62 @@ +import { Input, PartnerTitle } from "@/components/shared"; +import { HiOutlineArrowUpRight } from "react-icons/hi2"; + +export const metadata = { + title: "Partners - Sahil App", + description: + "We foster a culture of partnership and building strong, collaborative relationships. Partner with Sahil. Sahil logistics.", +}; + +export default function BusinessesPage() { + return ( + <> +
+ + +
+ +
+ + +
+ +
+
+ +
+ +
+ + ); +} diff --git a/apps/website/src/app/partners/couriers/page.tsx b/apps/website/src/app/partners/couriers/page.tsx new file mode 100644 index 00000000..cb886f90 --- /dev/null +++ b/apps/website/src/app/partners/couriers/page.tsx @@ -0,0 +1,59 @@ +import { Input, PartnerTitle, Select } from "@/components/shared"; +import { HiOutlineArrowUpRight } from "react-icons/hi2"; + +export const metadata = { + title: "Partners - Sahil App", + description: + "We foster a culture of partnership and building strong, collaborative relationships. Partner with Sahil. Sahil logistics.", +}; + +export default function CouriersPage() { + return ( + <> +
+ +
+
+ +
+ + +
+ +
+ + +
+