add bootstrap.component.tsx, implement dynamic nav generation
This commit is contained in:
parent
f059417376
commit
85587489f5
20
yoga-app/package-lock.json
generated
20
yoga-app/package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"@types/aos": "^3.0.7",
|
"@types/aos": "^3.0.7",
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
|
"@types/bootstrap": "^5.2.10",
|
||||||
"@types/pg": "^8.11.10",
|
"@types/pg": "^8.11.10",
|
||||||
"aos": "^2.3.4",
|
"aos": "^2.3.4",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
@ -1076,6 +1077,16 @@
|
|||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@popperjs/core": {
|
||||||
|
"version": "2.11.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
|
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/popperjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||||
@ -1121,6 +1132,15 @@
|
|||||||
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
|
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/bootstrap": {
|
||||||
|
"version": "5.2.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz",
|
||||||
|
"integrity": "sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||||
|
|||||||
@ -13,9 +13,9 @@
|
|||||||
"@types/aos": "^3.0.7",
|
"@types/aos": "^3.0.7",
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
|
"@types/bootstrap": "^5.2.10",
|
||||||
"@types/pg": "^8.11.10",
|
"@types/pg": "^8.11.10",
|
||||||
"aos": "^2.3.4",
|
"aos": "^2.3.4",
|
||||||
"bcryptjs": "2.4.3",
|
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"bootstrap": "^4.6.2",
|
"bootstrap": "^4.6.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|||||||
@ -9,10 +9,13 @@ import QuotesComponent from "@/components/quotes.component";
|
|||||||
import BlogPostsComponent from "@/components/blog.posts.component";
|
import BlogPostsComponent from "@/components/blog.posts.component";
|
||||||
import FooterComponent from "@/components/footer.component";
|
import FooterComponent from "@/components/footer.component";
|
||||||
import SubscribeComponent from "@/components/subscribe.component";
|
import SubscribeComponent from "@/components/subscribe.component";
|
||||||
|
import SubHeaderComponent from "@/components/subHeader.component";
|
||||||
|
import BootstrapComponent from "@/components/bootstrap.component";
|
||||||
|
|
||||||
export default function About() {
|
export default function About() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<SubHeaderComponent />
|
||||||
<OurServicesComponent />
|
<OurServicesComponent />
|
||||||
<AboutUsComponent />
|
<AboutUsComponent />
|
||||||
<OurSpecialitiesComponent />
|
<OurSpecialitiesComponent />
|
||||||
@ -23,6 +26,8 @@ export default function About() {
|
|||||||
<SubscribeComponent />
|
<SubscribeComponent />
|
||||||
<FooterComponent />
|
<FooterComponent />
|
||||||
<AosComponent />
|
<AosComponent />
|
||||||
|
<BootstrapComponent />
|
||||||
|
|
||||||
</>
|
</>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|||||||
29
yoga-app/src/app/faq/page.tsx
Normal file
29
yoga-app/src/app/faq/page.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from "react";
|
||||||
|
import AosComponent from "@/components/aos.component";
|
||||||
|
import OurServicesComponent from "@/components/our.services.component";
|
||||||
|
import AboutUsComponent from "@/components/about.us.component";
|
||||||
|
import OurSpecialitiesComponent from "@/components/our.specialities.component";
|
||||||
|
import ContactUsComponent from "@/components/contact.us.component";
|
||||||
|
import PricingComponent from "@/components/pricing.component";
|
||||||
|
import QuotesComponent from "@/components/quotes.component";
|
||||||
|
import BlogPostsComponent from "@/components/blog.posts.component";
|
||||||
|
import FooterComponent from "@/components/footer.component";
|
||||||
|
import SubscribeComponent from "@/components/subscribe.component";
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<OurServicesComponent />
|
||||||
|
<AboutUsComponent />
|
||||||
|
<OurSpecialitiesComponent />
|
||||||
|
<ContactUsComponent />
|
||||||
|
<PricingComponent />
|
||||||
|
<QuotesComponent />
|
||||||
|
<BlogPostsComponent />
|
||||||
|
<SubscribeComponent />
|
||||||
|
<FooterComponent />
|
||||||
|
<AosComponent />
|
||||||
|
</>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,8 +1,6 @@
|
|||||||
import type {Metadata, Viewport} from "next";
|
import type {Metadata, Viewport} from "next";
|
||||||
import "./globals.scss";
|
import "./globals.scss";
|
||||||
import {IconDescriptor} from "next/dist/lib/metadata/types/metadata-types";
|
import {IconDescriptor} from "next/dist/lib/metadata/types/metadata-types";
|
||||||
import MainHeaderComponent from "@/components/mainHeaderComponent";
|
|
||||||
|
|
||||||
|
|
||||||
const generateIconDescriptor = (rel: string, sizes: string, url: string): IconDescriptor => {
|
const generateIconDescriptor = (rel: string, sizes: string, url: string): IconDescriptor => {
|
||||||
// <link rel="apple-touch-icon" sizes="76x76"*/}
|
// <link rel="apple-touch-icon" sizes="76x76"*/}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import BlogPostsComponent from "@/components/blog.posts.component";
|
|||||||
import FooterComponent from "@/components/footer.component";
|
import FooterComponent from "@/components/footer.component";
|
||||||
import SubscribeComponent from "@/components/subscribe.component";
|
import SubscribeComponent from "@/components/subscribe.component";
|
||||||
import MainHeaderComponent from "@/components/mainHeaderComponent";
|
import MainHeaderComponent from "@/components/mainHeaderComponent";
|
||||||
|
import BootstrapComponent from "@/components/bootstrap.component";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
@ -25,6 +26,7 @@ export default function Home() {
|
|||||||
<SubscribeComponent />
|
<SubscribeComponent />
|
||||||
<FooterComponent />
|
<FooterComponent />
|
||||||
<AosComponent />
|
<AosComponent />
|
||||||
|
<BootstrapComponent />
|
||||||
</>
|
</>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|||||||
29
yoga-app/src/app/prices/page.tsx
Normal file
29
yoga-app/src/app/prices/page.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from "react";
|
||||||
|
import AosComponent from "@/components/aos.component";
|
||||||
|
import OurServicesComponent from "@/components/our.services.component";
|
||||||
|
import AboutUsComponent from "@/components/about.us.component";
|
||||||
|
import OurSpecialitiesComponent from "@/components/our.specialities.component";
|
||||||
|
import ContactUsComponent from "@/components/contact.us.component";
|
||||||
|
import PricingComponent from "@/components/pricing.component";
|
||||||
|
import QuotesComponent from "@/components/quotes.component";
|
||||||
|
import BlogPostsComponent from "@/components/blog.posts.component";
|
||||||
|
import FooterComponent from "@/components/footer.component";
|
||||||
|
import SubscribeComponent from "@/components/subscribe.component";
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<OurServicesComponent />
|
||||||
|
<AboutUsComponent />
|
||||||
|
<OurSpecialitiesComponent />
|
||||||
|
<ContactUsComponent />
|
||||||
|
<PricingComponent />
|
||||||
|
<QuotesComponent />
|
||||||
|
<BlogPostsComponent />
|
||||||
|
<SubscribeComponent />
|
||||||
|
<FooterComponent />
|
||||||
|
<AosComponent />
|
||||||
|
</>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
29
yoga-app/src/app/services/page.tsx
Normal file
29
yoga-app/src/app/services/page.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from "react";
|
||||||
|
import AosComponent from "@/components/aos.component";
|
||||||
|
import OurServicesComponent from "@/components/our.services.component";
|
||||||
|
import AboutUsComponent from "@/components/about.us.component";
|
||||||
|
import OurSpecialitiesComponent from "@/components/our.specialities.component";
|
||||||
|
import ContactUsComponent from "@/components/contact.us.component";
|
||||||
|
import PricingComponent from "@/components/pricing.component";
|
||||||
|
import QuotesComponent from "@/components/quotes.component";
|
||||||
|
import BlogPostsComponent from "@/components/blog.posts.component";
|
||||||
|
import FooterComponent from "@/components/footer.component";
|
||||||
|
import SubscribeComponent from "@/components/subscribe.component";
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<OurServicesComponent />
|
||||||
|
<AboutUsComponent />
|
||||||
|
<OurSpecialitiesComponent />
|
||||||
|
<ContactUsComponent />
|
||||||
|
<PricingComponent />
|
||||||
|
<QuotesComponent />
|
||||||
|
<BlogPostsComponent />
|
||||||
|
<SubscribeComponent />
|
||||||
|
<FooterComponent />
|
||||||
|
<AosComponent />
|
||||||
|
</>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
11
yoga-app/src/components/bootstrap.component.tsx
Normal file
11
yoga-app/src/components/bootstrap.component.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
export default function BootstrapComponent()
|
||||||
|
{
|
||||||
|
useEffect(()=>{
|
||||||
|
// @ts-ignore
|
||||||
|
import( "bootstrap/dist/js/bootstrap.bundle")
|
||||||
|
},[])
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
|
"use client"
|
||||||
import {FC} from "react";
|
import {FC} from "react";
|
||||||
import YogaImageComponent from "@/components/yoga.image.component";
|
import YogaImageComponent from "@/components/yoga.image.component";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import exp from "node:constants";
|
||||||
|
|
||||||
export interface MenuItem{
|
export interface MenuItem{
|
||||||
href?: string;
|
href?: string;
|
||||||
@ -14,6 +16,8 @@ export interface Props{
|
|||||||
|
|
||||||
const Nav: FC<Props> = ({menuItems}:Props) => {
|
const Nav: FC<Props> = ({menuItems}:Props) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header>
|
<header>
|
||||||
<div className="main_header">
|
<div className="main_header">
|
||||||
@ -43,9 +47,10 @@ const Nav: FC<Props> = ({menuItems}:Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface MenuItemProps{
|
interface MenuItemProps{
|
||||||
menuItem: MenuItem
|
menuItem: MenuItem,
|
||||||
|
dropdownItem?: boolean,
|
||||||
}
|
}
|
||||||
const MenuItemComponent: FC<MenuItemProps> = ({menuItem}: MenuItemProps) => {
|
const MenuItemComponent: FC<MenuItemProps> = ({menuItem, dropdownItem}: MenuItemProps) => {
|
||||||
if ( !menuItem ){
|
if ( !menuItem ){
|
||||||
return (<></>);
|
return (<></>);
|
||||||
}
|
}
|
||||||
@ -53,7 +58,7 @@ const MenuItemComponent: FC<MenuItemProps> = ({menuItem}: MenuItemProps) => {
|
|||||||
return (
|
return (
|
||||||
// <li className="nav-item active">
|
// <li className="nav-item active">
|
||||||
<li className={clsx("nav-item", {"active": menuItem.active})}>
|
<li className={clsx("nav-item", {"active": menuItem.active})}>
|
||||||
<a className="nav-link" href={menuItem.href}>{menuItem.label}</a>
|
<a className={clsx("nav-link", {"dropdown-item":dropdownItem})} href={menuItem.href}>{menuItem.label}</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -62,14 +67,17 @@ const MenuItemComponent: FC<MenuItemProps> = ({menuItem}: MenuItemProps) => {
|
|||||||
<li className={clsx("nav-item", "dropdown",{"active": menuItem.active})}>
|
<li className={clsx("nav-item", "dropdown",{"active": menuItem.active})}>
|
||||||
<a className="nav-link dropdown-toggle dropdown-color navbar-text-color" href="#"
|
<a className="nav-link dropdown-toggle dropdown-color navbar-text-color" href="#"
|
||||||
role="button" data-toggle="dropdown" aria-haspopup="true"
|
role="button" data-toggle="dropdown" aria-haspopup="true"
|
||||||
aria-expanded="false"> Blog </a>
|
aria-expanded="false">{menuItem.label}</a>
|
||||||
<div className="dropdown-menu drop-down-content">
|
<div className="dropdown-menu drop-down-content">
|
||||||
<ul className="list-unstyled drop-down-pages">
|
<ul className="list-unstyled drop-down-pages">
|
||||||
{
|
{
|
||||||
menuItem.children.map(item => <MenuItemComponent menuItem={item} />)
|
menuItem.children.map(item => <MenuItemComponent menuItem={item} dropdownItem={true}/>)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Nav;
|
||||||
|
|||||||
@ -1,90 +1,11 @@
|
|||||||
import YogaImageComponent from "@/components/yoga.image.component";
|
import YogaImageComponent from "@/components/yoga.image.component";
|
||||||
|
import Nav from "@/components/nav.component";
|
||||||
|
import {MAIN_MENU} from "@/util/const";
|
||||||
|
|
||||||
const SubHeaderComponent = () =>{
|
const SubHeaderComponent = () =>{
|
||||||
return (
|
return (
|
||||||
<div className="sub-banner-section">
|
<div className="sub-banner-section">
|
||||||
<header>
|
<Nav menuItems={MAIN_MENU} />
|
||||||
<div className="main_header">
|
|
||||||
<div className="container-fluid">
|
|
||||||
<nav className="navbar navbar-expand-lg navbar-light p-0">
|
|
||||||
<a className="navbar-brand" href="/index"><figure className="mb-0"><YogaImageComponent src="/assets/images/yogastic_logo.png" alt="" /></figure></a>
|
|
||||||
<button className="navbar-toggler collapsed" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span className="navbar-toggler-icon"></span>
|
|
||||||
<span className="navbar-toggler-icon"></span>
|
|
||||||
<span className="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div className="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul className="navbar-nav">
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="nav-link" href="/index">Home</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item active">
|
|
||||||
<a className="nav-link" href="/about">About Us</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="nav-link" href="/services">Services</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item dropdown">
|
|
||||||
<a className="nav-link dropdown-toggle dropdown-color navbar-text-color" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true"
|
|
||||||
aria-expanded="false"> Pages </a>
|
|
||||||
<div className="dropdown-menu drop-down-content">
|
|
||||||
<ul className="list-unstyled drop-down-pages">
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="dropdown-item nav-link" href="/pricing">Pricing</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="dropdown-item nav-link" href="/faq">Faq</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="nav-link" href="/team">Team</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item dropdown">
|
|
||||||
<a className="nav-link dropdown-toggle dropdown-color navbar-text-color" href="#" id="navbarDropdown2" role="button" data-toggle="dropdown" aria-haspopup="true"
|
|
||||||
aria-expanded="false"> Blog </a>
|
|
||||||
<div className="dropdown-menu drop-down-content">
|
|
||||||
<ul className="list-unstyled drop-down-pages">
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="dropdown-item nav-link" href="/single-post">Single Post</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="dropdown-item nav-link" href="/infinite-scroll">Infinite Scroll</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="dropdown-item nav-link" href="/load-more">Load More</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="dropdown-item nav-link" href="/one-column">One Column</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="dropdown-item nav-link" href="/two-column">Two Column</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="dropdown-item nav-link" href="/three-column">Three Column</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="dropdown-item nav-link" href="/three-colum-sidbar">Three Column Sidebar</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="dropdown-item nav-link" href="/four-column">Four Column</a>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="dropdown-item nav-link" href="/six-column">Six Column</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="nav-link contact_us" href="/contact">Contact Us</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<section className="banner-section">
|
<section className="banner-section">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
|||||||
37
yoga-app/src/util/const.ts
Normal file
37
yoga-app/src/util/const.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import {MenuItem} from "@/components/nav.component";
|
||||||
|
|
||||||
|
export const MAIN_MENU:MenuItem[] = [
|
||||||
|
{
|
||||||
|
label: 'Home',
|
||||||
|
href: '/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Rólunk',
|
||||||
|
href: '/about'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Szolgáltatásaink',
|
||||||
|
href: '/services'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Oldalak',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'Áraink',
|
||||||
|
href: '/prices'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'FAQ',
|
||||||
|
href: '/faq'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Team',
|
||||||
|
href: '/team'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Blog',
|
||||||
|
href: '/'
|
||||||
|
},
|
||||||
|
];
|
||||||
Loading…
Reference in New Issue
Block a user