├── src ├── types │ ├── index.ts │ ├── common.ts │ └── ApiResponse.ts ├── constants │ ├── storageKey.ts │ ├── globals.ts │ └── sidebarMenu.tsx ├── app │ ├── favicon.ico │ ├── globals.css │ ├── api │ │ └── auth │ │ │ └── [...nextauth] │ │ │ └── route.ts │ ├── (public) │ │ ├── loading.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── billing-address │ │ │ └── page.tsx │ │ ├── contact-us │ │ │ └── page.tsx │ │ └── services │ │ │ └── [id] │ │ │ └── page.tsx │ ├── (dashboard) │ │ ├── layout.tsx │ │ └── dashboard │ │ │ ├── add-feedback │ │ │ └── page.tsx │ │ │ ├── categories │ │ │ ├── add │ │ │ │ └── page.tsx │ │ │ ├── edit │ │ │ │ └── [id] │ │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ │ ├── manage-admins │ │ │ ├── add │ │ │ │ └── page.tsx │ │ │ ├── page.tsx │ │ │ └── edit │ │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ │ ├── manage-tasks │ │ │ ├── page.tsx │ │ │ ├── add │ │ │ │ └── page.tsx │ │ │ └── edit │ │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ │ ├── manage-sellers │ │ │ ├── page.tsx │ │ │ └── edit │ │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ │ ├── manage-buyers │ │ │ ├── page.tsx │ │ │ └── edit │ │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ │ ├── manage-orders │ │ │ └── page.tsx │ │ │ └── page.tsx │ ├── layout.tsx │ ├── not-found.tsx │ ├── register │ │ ├── seller │ │ │ └── page.tsx │ │ └── buyer │ │ │ └── page.tsx │ └── login │ │ └── page.tsx ├── assets │ ├── images │ │ ├── avatar.jpg │ │ ├── ed-dev.jpg │ │ ├── img_eliza.webp │ │ ├── img_harry.webp │ │ ├── img_heart.webp │ │ ├── img_sarah.webp │ │ ├── img_man-thinking.webp │ │ └── img_paper-section-top.webp │ └── svg │ │ ├── ico_tick-2.svg │ │ └── ico_star.svg ├── components │ ├── ui │ │ ├── Navbar.css │ │ ├── PageLoading.tsx │ │ ├── Container.tsx │ │ ├── Button.tsx │ │ ├── BreadCrumb.tsx │ │ ├── AdminContent.tsx │ │ ├── FormDatePicker.css │ │ ├── AdminNavbar.tsx │ │ ├── Form.tsx │ │ ├── FormTextArea.tsx │ │ ├── FormInput.tsx │ │ ├── FormRating.tsx │ │ ├── FormSelect.tsx │ │ ├── FormDatePicker.tsx │ │ ├── Sidebar.tsx │ │ ├── Footer.tsx │ │ ├── Navbar.tsx │ │ └── FormImageUpload.tsx │ ├── home │ │ ├── Guarantees.css │ │ ├── AsapFeatures.tsx │ │ ├── FreeBies.tsx │ │ ├── TroubleSolution.tsx │ │ ├── FreeBies.css │ │ ├── Service.tsx │ │ ├── HomepageBanner.tsx │ │ ├── Tasks.tsx │ │ └── Guarantees.tsx │ ├── map │ │ └── BuyerMap.tsx │ └── services │ │ ├── AddReview.tsx │ │ ├── ServiceDetailsSidebar.tsx │ │ ├── ServiceReview.tsx │ │ └── ServiceReviews.tsx ├── utils │ ├── cn.ts │ ├── schema-validator.ts │ └── local-storage.ts ├── helpers │ ├── config │ │ └── envConfig.ts │ ├── jwthelpers │ │ └── jwthelpers.ts │ └── axios │ │ ├── axiosBaseQuery.ts │ │ └── axiosInstance.ts ├── redux │ ├── rootReducer.ts │ ├── hooks.ts │ ├── features │ │ ├── filter │ │ │ └── filterSlice.ts │ │ ├── api │ │ │ └── apiSlice.ts │ │ ├── tasks │ │ │ ├── taskSlice.ts │ │ │ └── tasksApi.ts │ │ ├── taskReview │ │ │ ├── taskReviewSlice.ts │ │ │ └── taskReviewApi.ts │ │ ├── profile │ │ │ └── profileApi.ts │ │ ├── admin │ │ │ └── adminApi.ts │ │ ├── buyer │ │ │ └── buyerApi.ts │ │ ├── seller │ │ │ └── sellerApi.ts │ │ ├── testimonial │ │ │ └── testimonialApi.ts │ │ ├── order │ │ │ └── orderApi.ts │ │ ├── auth │ │ │ └── authApi.ts │ │ └── categories │ │ │ └── categoriesApi.ts │ ├── tag-types.ts │ └── store.ts ├── lib │ ├── Providers.tsx │ ├── LocalStorageProviders.tsx │ └── authOptions.ts ├── services │ └── auth.services.ts └── middleware.ts ├── .eslintrc.json ├── public ├── icon-192x192.png ├── icon-256x256.png ├── icon-384x384.png ├── icon-512x512.png ├── vercel.svg ├── manifest.json └── next.svg ├── postcss.config.js ├── next.config.js ├── .gitignore ├── tsconfig.json ├── README.md ├── package.json └── tailwind.config.ts /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./common"; -------------------------------------------------------------------------------- /src/constants/storageKey.ts: -------------------------------------------------------------------------------- 1 | export const AUTH_KEY = "accessToken"; 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | // eslintrc json -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /public/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/public/icon-192x192.png -------------------------------------------------------------------------------- /public/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/public/icon-256x256.png -------------------------------------------------------------------------------- /public/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/public/icon-384x384.png -------------------------------------------------------------------------------- /public/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/public/icon-512x512.png -------------------------------------------------------------------------------- /src/assets/images/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/src/assets/images/avatar.jpg -------------------------------------------------------------------------------- /src/assets/images/ed-dev.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/src/assets/images/ed-dev.jpg -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | // postcss config -------------------------------------------------------------------------------- /src/assets/images/img_eliza.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/src/assets/images/img_eliza.webp -------------------------------------------------------------------------------- /src/assets/images/img_harry.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/src/assets/images/img_harry.webp -------------------------------------------------------------------------------- /src/assets/images/img_heart.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/src/assets/images/img_heart.webp -------------------------------------------------------------------------------- /src/assets/images/img_sarah.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/src/assets/images/img_sarah.webp -------------------------------------------------------------------------------- /src/assets/images/img_man-thinking.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/src/assets/images/img_man-thinking.webp -------------------------------------------------------------------------------- /src/components/ui/Navbar.css: -------------------------------------------------------------------------------- 1 | details > summary { 2 | list-style: none; 3 | } 4 | details > summary::-webkit-details-marker { 5 | display: none; 6 | } -------------------------------------------------------------------------------- /src/assets/images/img_paper-section-top.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hazrat-Ali9/Online-work-platform-client/HEAD/src/assets/images/img_paper-section-top.webp -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* smooth scroll */ 6 | 7 | * { 8 | scroll-behavior: smooth; 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import { twMerge, ClassNameValue } from "tailwind-merge"; 3 | 4 | export const cn = (...classes: ClassNameValue[]) => twMerge(clsx(...classes)); 5 | -------------------------------------------------------------------------------- /src/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth"; 2 | import { authOptions } from "@/lib/authOptions"; 3 | 4 | const handler = NextAuth(authOptions); 5 | export { handler as GET, handler as POST }; 6 | -------------------------------------------------------------------------------- /src/helpers/config/envConfig.ts: -------------------------------------------------------------------------------- 1 | export const getBaseUrl = (): string => { 2 | // return process.env.BACKEND_URL || "http://localhost:5000/api/v1"; 3 | return "http://localhost:5000/api/v1"; 4 | }; 5 | 6 | export const envConfig = { 7 | siteUrl: process.env.NEXTAUTH_URL || "http://localhost:3000", 8 | }; 9 | -------------------------------------------------------------------------------- /src/app/(public)/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Loading = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export default Loading; 12 | -------------------------------------------------------------------------------- /src/helpers/jwthelpers/jwthelpers.ts: -------------------------------------------------------------------------------- 1 | import jwt, { Secret } from "jsonwebtoken"; 2 | 3 | const verifyToken = (token: string, secret: Secret) => { 4 | try { 5 | return jwt.verify(token, secret); 6 | } catch (error) { 7 | return null; 8 | } 9 | }; 10 | 11 | export const jwtHelpers = { verifyToken }; 12 | -------------------------------------------------------------------------------- /src/components/ui/PageLoading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const PageLoading = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export default PageLoading; 12 | -------------------------------------------------------------------------------- /src/redux/rootReducer.ts: -------------------------------------------------------------------------------- 1 | import { apiSlice } from "./features/api/apiSlice"; 2 | import filterReducer from "./features/filter/filterSlice"; 3 | import taskReducer from "./features/tasks/taskSlice"; 4 | 5 | export const reducer = { 6 | [apiSlice.reducerPath]: apiSlice.reducer, 7 | tasks: taskReducer, 8 | filter: filterReducer, 9 | }; 10 | -------------------------------------------------------------------------------- /src/redux/hooks.ts: -------------------------------------------------------------------------------- 1 | import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; 2 | import type { RootState, AppDispatch } from "./store"; 3 | 4 | // Use throughout your app instead of plain `useDispatch` and `useSelector` 5 | export const useAppDispatch: () => AppDispatch = useDispatch; 6 | export const useAppSelector: TypedUseSelectorHook = useSelector; 7 | -------------------------------------------------------------------------------- /src/components/ui/Container.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/utils/cn"; 2 | import React from "react"; 3 | 4 | const Container = ({ 5 | children, 6 | className, 7 | }: { 8 | children: React.ReactNode; 9 | className?: string; 10 | }) => { 11 | return ( 12 |
13 | {children} 14 |
15 | ); 16 | }; 17 | 18 | export default Container; 19 | -------------------------------------------------------------------------------- /src/utils/schema-validator.ts: -------------------------------------------------------------------------------- 1 | export const getErrorMessageByPropertyName = ( 2 | obj: Record, 3 | propertyPath: string 4 | ) => { 5 | 6 | const properties = propertyPath?.split("."); 7 | let value = obj; 8 | 9 | for (let prop of properties) { 10 | if (value[prop]) { 11 | value = value[prop]; 12 | } else { 13 | return undefined; 14 | } 15 | } 16 | 17 | return value.message; 18 | }; 19 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | // next config 3 | const withPWA = require("next-pwa")({ 4 | dest: "public", 5 | register: true, 6 | skipWaiting: true, 7 | disable: process.env.NODE_ENV === "development", 8 | }); 9 | 10 | const nextConfig = withPWA({ 11 | experimental: { 12 | serverActions: true, 13 | }, 14 | images: { 15 | domains: ["res.cloudinary.com", "daisyui.com"], 16 | }, 17 | }); 18 | 19 | module.exports = nextConfig; 20 | -------------------------------------------------------------------------------- /src/components/ui/Button.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/utils/cn"; 2 | import React from "react"; 3 | 4 | type BtnProps = { 5 | children: React.ReactNode; 6 | className?: string; 7 | type?: "button" | "submit" | "reset"; 8 | }; 9 | 10 | const Button = ({ children, className, ...props }: BtnProps) => { 11 | return ( 12 | 15 | ); 16 | }; 17 | 18 | export default Button; 19 | -------------------------------------------------------------------------------- /src/redux/features/filter/filterSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | interface TaskState { 4 | search: string; 5 | } 6 | 7 | const initialState: TaskState = { 8 | search: "", 9 | }; 10 | 11 | const filterSlice = createSlice({ 12 | name: "filter", 13 | initialState, 14 | reducers: { 15 | setSearch: (state, action) => { 16 | state.search = action.payload; 17 | }, 18 | }, 19 | }); 20 | 21 | export const { setSearch } = filterSlice.actions; 22 | export default filterSlice.reducer; 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /src/redux/features/api/apiSlice.ts: -------------------------------------------------------------------------------- 1 | import { axiosBaseQuery } from "@/helpers/axios/axiosBaseQuery"; 2 | import { getBaseUrl } from "@/helpers/config/envConfig"; 3 | import { tagTypesList } from "@/redux/tag-types"; 4 | import { createApi } from "@reduxjs/toolkit/query/react"; 5 | 6 | // Define a service using a base URL and expected endpoints 7 | export const apiSlice = createApi({ 8 | reducerPath: "api", 9 | baseQuery: axiosBaseQuery({ baseUrl: getBaseUrl() }), 10 | endpoints: () => ({}), 11 | tagTypes: tagTypesList, 12 | }); 13 | -------------------------------------------------------------------------------- /src/components/ui/BreadCrumb.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | 4 | const BreadCrumb = ({ 5 | items, 6 | }: { 7 | items: { 8 | name: string; 9 | slug: string; 10 | }[]; 11 | }) => { 12 | return ( 13 |
14 |
    15 | {items.map((item, index) => ( 16 |
  • 17 | {item.name} 18 |
  • 19 | ))} 20 |
21 |
22 | ); 23 | }; 24 | 25 | export default BreadCrumb; 26 | -------------------------------------------------------------------------------- /src/redux/features/tasks/taskSlice.ts: -------------------------------------------------------------------------------- 1 | import { Task } from "@/types/ApiResponse"; 2 | import { createSlice } from "@reduxjs/toolkit"; 3 | 4 | interface TaskState { 5 | task: Partial; 6 | } 7 | 8 | const initialState: TaskState = { 9 | task: {}, 10 | }; 11 | 12 | const taskSlice = createSlice({ 13 | name: "task", 14 | initialState, 15 | reducers: { 16 | setTask: (state, action) => { 17 | state.task = action.payload; 18 | }, 19 | }, 20 | }); 21 | 22 | export const { setTask } = taskSlice.actions; 23 | export default taskSlice.reducer; 24 | -------------------------------------------------------------------------------- /src/redux/features/taskReview/taskReviewSlice.ts: -------------------------------------------------------------------------------- 1 | import { Task } from "@/types/ApiResponse"; 2 | import { createSlice } from "@reduxjs/toolkit"; 3 | 4 | interface TaskState { 5 | task: Partial; 6 | } 7 | 8 | const initialState: TaskState = { 9 | task: {}, 10 | }; 11 | 12 | const taskSlice = createSlice({ 13 | name: "task", 14 | initialState, 15 | reducers: { 16 | setTask: (state, action) => { 17 | state.task = action.payload; 18 | }, 19 | }, 20 | }); 21 | 22 | export const { setTask } = taskSlice.actions; 23 | export default taskSlice.reducer; 24 | -------------------------------------------------------------------------------- /src/components/ui/AdminContent.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState } from "react"; 4 | import AdminNavbar from "./AdminNavbar"; 5 | 6 | const AdminContent = ({ children }: { children: React.ReactNode }) => { 7 | const [isOpen, setIsOpen] = useState(true); 8 | 9 | return ( 10 |
15 | 16 | {children} 17 |
18 | ); 19 | }; 20 | 21 | export default AdminContent; 22 | -------------------------------------------------------------------------------- /src/redux/tag-types.ts: -------------------------------------------------------------------------------- 1 | export enum tagTypes { 2 | user = "user", 3 | admin = "admin", 4 | seller = "seller", 5 | buyer = "buyer", 6 | profile = "profile", 7 | task = "task", 8 | taskReview = "task-review", 9 | order = "order", 10 | testimonial = "testimonial", 11 | category = "category", 12 | } 13 | 14 | export const tagTypesList = [ 15 | tagTypes.user, 16 | tagTypes.admin, 17 | tagTypes.buyer, 18 | tagTypes.seller, 19 | tagTypes.profile, 20 | tagTypes.task, 21 | tagTypes.taskReview, 22 | tagTypes.order, 23 | tagTypes.testimonial, 24 | tagTypes.category, 25 | ]; 26 | -------------------------------------------------------------------------------- /src/utils/local-storage.ts: -------------------------------------------------------------------------------- 1 | export const setToLocalStorage = (key: string, token: string) => { 2 | if (!key || typeof window === "undefined") { 3 | return ""; 4 | } 5 | return localStorage.setItem(key, token); 6 | }; 7 | 8 | export const getFromLocalStorage = (key: string) => { 9 | if (!key || typeof window === "undefined") { 10 | return ""; 11 | } 12 | return localStorage.getItem(key); 13 | }; 14 | 15 | export const removeFromLocalStorage = (key: string) => { 16 | if (!key || typeof window === "undefined") { 17 | return ""; 18 | } 19 | return localStorage.removeItem(key); 20 | }; -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { apiSlice } from "./features/api/apiSlice"; 2 | import { reducer } from "./rootReducer"; 3 | import { configureStore } from "@reduxjs/toolkit"; 4 | 5 | export const store = configureStore({ 6 | reducer, 7 | middleware: (getDefaultMiddleware) => 8 | getDefaultMiddleware().concat(apiSlice.middleware), 9 | }); 10 | 11 | // Infer the `RootState` and `AppDispatch` types from the store itself 12 | export type RootState = ReturnType; 13 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} 14 | export type AppDispatch = typeof store.dispatch; 15 | -------------------------------------------------------------------------------- /src/lib/Providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { store } from "@/redux/store"; 4 | import { SessionProvider } from "next-auth/react"; 5 | import { ReactNode } from "react"; 6 | import { Toaster } from "react-hot-toast"; 7 | import { Provider } from "react-redux"; 8 | 9 | export default function Providers({ 10 | children, 11 | session, 12 | }: { 13 | children: ReactNode; 14 | session: any; 15 | }) { 16 | return ( 17 | 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/assets/svg/ico_tick-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/ui/FormDatePicker.css: -------------------------------------------------------------------------------- 1 | .rdp { 2 | --rdp-cell-size: 40px; 3 | --rdp-caption-font-size: 18px; 4 | --rdp-accent-color: #570DF8; 5 | --rdp-background-color: #000; 6 | --rdp-accent-color-dark: #3003e1; 7 | --rdp-background-color-dark: #180270; 8 | --rdp-outline: 2px solid var(--rdp-accent-color); 9 | --rdp-outline-selected: 3px solid var(--rdp-accent-color); 10 | --rdp-selected-color: #fff; 11 | margin: 1em; 12 | } 13 | 14 | .rdp-months { 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | } 19 | 20 | 21 | .rdp-dropdown { 22 | background-color: #000; 23 | } -------------------------------------------------------------------------------- /src/app/(public)/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from "@/components/ui/Footer"; 2 | import Navbar from "@/components/ui/Navbar"; 3 | import LocalStorageProvider from "@/lib/LocalStorageProviders"; 4 | import { authOptions } from "@/lib/authOptions"; 5 | import { getServerSession } from "next-auth"; 6 | import React from "react"; 7 | 8 | const PublicLayout = async ({ children }: { children: React.ReactNode }) => { 9 | const session: any = await getServerSession(authOptions); 10 | 11 | 12 | return ( 13 | 14 | 15 | {children} 16 |