mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 01:38:44 +00:00
Merge branch 'master' into bflat-storyteller-landing
This commit is contained in:
15 files changed
+131
-51
No files matched your search
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Define links to third party sources here so we can update them in one place.
|
||||
*/
|
||||
class ThirdPartyLinks {
|
||||
// This should only need to change if our Nitro expires.
|
||||
// Old FakeYou Discord invite link: https://discord.gg/H72KFXm
|
||||
public static readonly FAKEYOU_DISCORD = "https://discord.gg/fakeyou";
|
||||
|
||||
// A link to Twitter that will open a "Follow" prompt
|
||||
public static readonly FAKEYOU_TWITTER_WITH_FOLLOW_INTENT = "https://twitter.com/intent/follow?screen_name=FakeYouApp";
|
||||
|
||||
public static readonly FAKEYOU_PATREON = "https://www.patreon.com/fakeyou";
|
||||
}
|
||||
|
||||
export { ThirdPartyLinks }
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ThirdPartyLinks } from "../constants/ThirdPartyLinks";
|
||||
|
||||
interface Props {
|
||||
text?: string;
|
||||
@@ -24,7 +25,7 @@ function DiscordLink(props: Props) {
|
||||
);
|
||||
return (
|
||||
<a
|
||||
href="https://discord.gg/fakeyou"
|
||||
href={ThirdPartyLinks.FAKEYOU_DISCORD}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ThirdPartyLinks } from "../constants/ThirdPartyLinks";
|
||||
|
||||
interface Props {
|
||||
hideIcon?: boolean;
|
||||
@@ -35,7 +36,7 @@ function DiscordLink2(props: Props) {
|
||||
|
||||
return (
|
||||
<a
|
||||
href="https://discord.gg/H72KFXm"
|
||||
href={ThirdPartyLinks.FAKEYOU_DISCORD}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faPatreon } from "@fortawesome/free-brands-svg-icons";
|
||||
import { ThirdPartyLinks } from "../constants/ThirdPartyLinks";
|
||||
|
||||
interface Props {
|
||||
text?: string;
|
||||
@@ -24,7 +25,7 @@ function PatreonLink(props: Props) {
|
||||
);
|
||||
return (
|
||||
<a
|
||||
href="https://www.patreon.com/FakeYou"
|
||||
href={ThirdPartyLinks.FAKEYOU_PATREON}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import { faTwitter } from "@fortawesome/free-brands-svg-icons";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ThirdPartyLinks } from "../constants/ThirdPartyLinks";
|
||||
|
||||
interface Props {
|
||||
hideIcon?: boolean;
|
||||
@@ -35,7 +36,7 @@ function TwitterLink(props: Props) {
|
||||
|
||||
return (
|
||||
<a
|
||||
href="https://twitter.com/intent/follow?screen_name=FakeYouApp"
|
||||
href={ThirdPartyLinks.FAKEYOU_TWITTER_WITH_FOLLOW_INTENT}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@@ -29,7 +29,9 @@ function TopNav(props: Props) {
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#offcanvasNavbar2"
|
||||
aria-controls="offcanvasNavbar2"
|
||||
title="toggle navbar"
|
||||
>
|
||||
<span className="sr-only">Navbar toggle</span>
|
||||
<span className="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div
|
||||
|
||||
@@ -177,19 +177,18 @@ class App extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// Handle redesign
|
||||
console.log("componentWillMount");
|
||||
|
||||
// Check to see if there is a cookie for darkMode;
|
||||
if (!window.localStorage.getItem("darkMode")) {
|
||||
// if not, set one to false to ensure we are defualting to dark mode.
|
||||
window.localStorage.setItem("darkMode", "false");
|
||||
}
|
||||
|
||||
// Check to see if there is a cookie for lowSpec;
|
||||
if (!window.localStorage.getItem("lowSpec")) {
|
||||
// if not, set one to false to ensure we are defualting to dark mode.
|
||||
window.localStorage.setItem("lowSpec", "false");
|
||||
// if not, set one to true to ensure we are defualting to low spec mode.
|
||||
window.localStorage.setItem("lowSpec", "true");
|
||||
}
|
||||
|
||||
require("./AppOld.scss");
|
||||
require("./v2/view/_css/footer.scss");
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
faTwitter,
|
||||
faFacebook,
|
||||
} from "@fortawesome/free-brands-svg-icons";
|
||||
import { ThirdPartyLinks } from "@storyteller/components/src/constants/ThirdPartyLinks";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
@@ -83,9 +84,10 @@ function NewFooterNavFc(props: Props) {
|
||||
<div className="d-flex justify-content-center gap-4">
|
||||
<a
|
||||
className="social-icon"
|
||||
href="https://discord.gg/fakeyou"
|
||||
href={ThirdPartyLinks.FAKEYOU_DISCORD}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Join our Discord Server"
|
||||
>
|
||||
<FontAwesomeIcon icon={faDiscord} className="me-2" />
|
||||
</a>
|
||||
@@ -94,14 +96,16 @@ function NewFooterNavFc(props: Props) {
|
||||
href="https://twitch.tv/FakeYouLabs"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Subscribe to our Twitch Channel"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTwitch} className="me-2" />
|
||||
</a>
|
||||
<a
|
||||
className="social-icon"
|
||||
href="https://twitter.com/intent/follow?screen_name=FakeYouApp"
|
||||
href={ThirdPartyLinks.FAKEYOU_TWITTER_WITH_FOLLOW_INTENT}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Follow us on Twitter"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTwitter} className="me-2" />
|
||||
</a>
|
||||
@@ -110,14 +114,16 @@ function NewFooterNavFc(props: Props) {
|
||||
href="https://facebook.com/vocodes"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Like us on Facebook"
|
||||
>
|
||||
<FontAwesomeIcon icon={faFacebook} className="me-2" />
|
||||
</a>
|
||||
<a
|
||||
className="social-icon"
|
||||
href="https://www.patreon.com/FakeYou"
|
||||
href={ThirdPartyLinks.FAKEYOU_PATREON}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Support us by becoming a patreon"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPatreon} className="me-2" />
|
||||
</a>
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
faComputer,
|
||||
faLaptop,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faPatreon } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faDiscord, faPatreon } from "@fortawesome/free-brands-svg-icons";
|
||||
import { Logout } from "@storyteller/components/src/api/session/Logout";
|
||||
import { Gravatar } from "@storyteller/components/src/elements/Gravatar";
|
||||
import {
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
import Tippy from "@tippyjs/react";
|
||||
import "tippy.js/dist/tippy.css";
|
||||
import "tippy.js/animations/shift-away.css";
|
||||
import { ThirdPartyLinks } from "@storyteller/components/src/constants/ThirdPartyLinks";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
@@ -51,8 +52,9 @@ function NewTopNavFc(props: Props) {
|
||||
const [darkModes, toggleDarkModes] = useState(
|
||||
defaultColourView === "false" ? true : false
|
||||
);
|
||||
|
||||
const [lowSpecView, toggleLowSpecs] = useState(
|
||||
defaultLowSpecView === "true" ? true : false
|
||||
defaultLowSpecView === "false" ? true : false
|
||||
);
|
||||
|
||||
let history = useHistory();
|
||||
@@ -225,9 +227,8 @@ function NewTopNavFc(props: Props) {
|
||||
</p> */}
|
||||
|
||||
<Tippy
|
||||
content={`${
|
||||
darkModes ? "Toggle Light Mode" : "Toggle Dark Mode"
|
||||
}`}
|
||||
content={`${darkModes ? "Toggle Light Mode" : "Toggle Dark Mode"
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className={`btn btn-toggle ${darkModes ? "dark" : ""}`}
|
||||
@@ -237,9 +238,8 @@ function NewTopNavFc(props: Props) {
|
||||
</button>
|
||||
</Tippy>
|
||||
<Tippy
|
||||
content={`${
|
||||
lowSpecView ? "Turn on animations" : "Turn off animations"
|
||||
}`}
|
||||
content={`${lowSpecView ? "Turn on animations" : "Turn off animations"
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className={`btn btn-toggle ${darkModes ? "dark" : ""}`}
|
||||
@@ -325,7 +325,7 @@ function NewTopNavFc(props: Props) {
|
||||
<a
|
||||
className="nav-link dropdown-toggle"
|
||||
href="/"
|
||||
id="offcanvasNavbarLgDropdown"
|
||||
id="offcanvasNavbarLgDropdown-tts"
|
||||
role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
@@ -335,7 +335,7 @@ function NewTopNavFc(props: Props) {
|
||||
</a>
|
||||
<ul
|
||||
className="dropdown-menu"
|
||||
aria-labelledby="offcanvasNavbarLgDropdown"
|
||||
aria-labelledby="offcanvasNavbarLgDropdown-tts"
|
||||
>
|
||||
<li data-bs-toggle="offcanvas">
|
||||
<Link className="dropdown-item" to="/">
|
||||
@@ -357,46 +357,55 @@ function NewTopNavFc(props: Props) {
|
||||
<a
|
||||
className="nav-link dropdown-toggle"
|
||||
href="/"
|
||||
id="offcanvasNavbarLgDropdown"
|
||||
id="offcanvasNavbarLgDropdown-community"
|
||||
role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-label="Community dropdown"
|
||||
>
|
||||
<FontAwesomeIcon icon={faUsers} className="me-2" />
|
||||
Community
|
||||
</a>
|
||||
<ul
|
||||
className="dropdown-menu"
|
||||
aria-labelledby="offcanvasNavbarLgDropdown"
|
||||
aria-labelledby="offcanvasNavbarLgDropdown-community"
|
||||
>
|
||||
<li data-bs-toggle="offcanvas">
|
||||
<Link className="dropdown-item" to="/contribute">
|
||||
<Link className="dropdown-item" to="/contribute" title="to Upload page">
|
||||
<FontAwesomeIcon icon={faUpload} className="me-2" />
|
||||
Contribute/Upload
|
||||
</Link>
|
||||
</li>
|
||||
<li data-bs-toggle="offcanvas">
|
||||
<Link className="dropdown-item" to="/leaderboard">
|
||||
<Link className="dropdown-item" to="/leaderboard" title="to leaderboard">
|
||||
<FontAwesomeIcon icon={faTrophy} className="me-2" />
|
||||
Leaderboard
|
||||
</Link>
|
||||
</li>
|
||||
<li data-bs-toggle="offcanvas">
|
||||
<Link className="dropdown-item" to="/patrons">
|
||||
<a className="dropdown-item"
|
||||
href={ThirdPartyLinks.FAKEYOU_DISCORD}
|
||||
title="discord chat"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<FontAwesomeIcon icon={faDiscord} className="me-2" />
|
||||
Discord Chat
|
||||
</a>
|
||||
</li>
|
||||
<li data-bs-toggle="offcanvas">
|
||||
<Link className="dropdown-item" to="/patrons" title="to patron list">
|
||||
<FontAwesomeIcon icon={faPatreon} className="me-2" />
|
||||
Patrons
|
||||
</Link>
|
||||
</li>
|
||||
<li data-bs-toggle="offcanvas">
|
||||
<Link className="dropdown-item" to="/firehose">
|
||||
<Link className="dropdown-item" to="/firehose" title="to feed">
|
||||
<FontAwesomeIcon icon={faList} className="me-2" />
|
||||
Feed
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<div className="dropdown-divider"></div>
|
||||
</li>
|
||||
<hr className='dropdown-divider' />
|
||||
<li data-bs-toggle="offcanvas">
|
||||
<Link className="dropdown-item" to={myDataLink}>
|
||||
<Link className="dropdown-item" to={myDataLink} title="my profile">
|
||||
<FontAwesomeIcon icon={faUser} className="me-2" />
|
||||
My Data
|
||||
</Link>
|
||||
@@ -405,13 +414,13 @@ function NewTopNavFc(props: Props) {
|
||||
</li>
|
||||
|
||||
<li data-bs-toggle="offcanvas" className="nav-item d-lg-none">
|
||||
<Link className="nav-link" aria-current="page" to="/about">
|
||||
<Link className="nav-link" aria-current="page" to="/about" title="About Us">
|
||||
About
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li data-bs-toggle="offcanvas" className="nav-item d-lg-none">
|
||||
<Link className="nav-link" aria-current="page" to="/terms">
|
||||
<Link className="nav-link" aria-current="page" title="Terms of Use" to="/terms">
|
||||
Terms of Use
|
||||
</Link>
|
||||
</li>
|
||||
@@ -421,13 +430,14 @@ function NewTopNavFc(props: Props) {
|
||||
<a
|
||||
className="nav-link"
|
||||
aria-current="page"
|
||||
title="to API"
|
||||
href={FrontendUrlConfig.developerDocs()}
|
||||
>
|
||||
Developers
|
||||
</a>
|
||||
</li>
|
||||
<li className="d-lg-none">
|
||||
<div className="dropdown-divider dropdown-divider-white mt-3"></div>
|
||||
<hr className="dropdown-divider dropdown-divider-white" />
|
||||
</li>
|
||||
|
||||
<li className="ps-3 d-lg-none">
|
||||
@@ -445,16 +455,15 @@ function NewTopNavFc(props: Props) {
|
||||
</li>
|
||||
|
||||
<li className="d-lg-none">
|
||||
<div className="dropdown-divider dropdown-divider-white mt-3"></div>
|
||||
<hr className="dropdown-divider dropdown-divider-white" />
|
||||
</li>
|
||||
|
||||
<li className="ps-3 d-lg-none">
|
||||
<div className="d-flex gap-4 py-2">
|
||||
<div className="top-bar-text mobile">Options:</div>
|
||||
<Tippy
|
||||
content={`${
|
||||
darkModes ? "Toggle Light Mode" : "Toggle Dark Mode"
|
||||
}`}
|
||||
content={`${darkModes ? "Toggle Light Mode" : "Toggle Dark Mode"
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className={`btn btn-toggle ${darkModes ? "dark" : ""}`}
|
||||
@@ -464,11 +473,10 @@ function NewTopNavFc(props: Props) {
|
||||
</button>
|
||||
</Tippy>
|
||||
<Tippy
|
||||
content={`${
|
||||
lowSpecView
|
||||
? "Turn on animations"
|
||||
: "Turn off animations"
|
||||
}`}
|
||||
content={`${lowSpecView
|
||||
? "Turn on animations"
|
||||
: "Turn off animations"
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className={`btn btn-toggle ${darkModes ? "dark" : ""}`}
|
||||
@@ -484,7 +492,7 @@ function NewTopNavFc(props: Props) {
|
||||
</li>
|
||||
|
||||
<li className="d-lg-none">
|
||||
<div className="dropdown-divider dropdown-divider-white"></div>
|
||||
<hr className="dropdown-divider dropdown-divider-white" />
|
||||
</li>
|
||||
</ul>
|
||||
<div className="d-grid d-flex justify-content-start align-items-center pt-4 ps-3 pt-lg-0 ps-lg-0">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { container, item, panel } from "../../../../data/animation";
|
||||
import { ThirdPartyLinks } from "@storyteller/components/src/constants/ThirdPartyLinks";
|
||||
|
||||
interface Props {}
|
||||
|
||||
@@ -28,7 +29,7 @@ function TermsPage(props: Props) {
|
||||
We do not condone the use of FakeYou for any type of deception,
|
||||
slur, abuse, or mistreatment of any individual or group. Please
|
||||
report such abuses to our{" "}
|
||||
<a href="https://discord.gg/H72KFXm">
|
||||
<a href={ThirdPartyLinks.FAKEYOU_DISCORD}>
|
||||
community staff on Discord
|
||||
</a>
|
||||
. Bad actors will have their access revoked and materials deleted.
|
||||
|
||||
@@ -6,6 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faPatreon } from "@fortawesome/free-brands-svg-icons";
|
||||
import { motion } from "framer-motion";
|
||||
import { container, item, image, panel } from "../../../data/animation";
|
||||
import { ThirdPartyLinks } from "@storyteller/components/src/constants/ThirdPartyLinks";
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
}
|
||||
@@ -29,7 +30,7 @@ function PatronPage(props: Props) {
|
||||
|
||||
<motion.div className="d-flex" variants={item}>
|
||||
<a
|
||||
href="https://www.patreon.com/fakeyou"
|
||||
href={ThirdPartyLinks.FAKEYOU_PATREON}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="btn btn-primary"
|
||||
|
||||
@@ -25,6 +25,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faEdit, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { motion } from "framer-motion";
|
||||
import { container, item, panel } from "../../../../data/animation";
|
||||
import { ThirdPartyLinks } from "@storyteller/components/src/constants/ThirdPartyLinks";
|
||||
|
||||
interface EnqueueJobResponsePayload {
|
||||
success: boolean;
|
||||
@@ -190,7 +191,7 @@ function W2lTemplateViewFc(props: Props) {
|
||||
<span>
|
||||
Not yet (ask for approval in our
|
||||
<a
|
||||
href="https://discord.gg/H72KFXm"
|
||||
href={ThirdPartyLinks.FAKEYOU_DISCORD}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@@ -27,6 +27,7 @@ import { useEffect } from "react";
|
||||
import gsap from "gsap";
|
||||
import SplitType from "split-type";
|
||||
import Scene from "./Scene";
|
||||
import { ThirdPartyLinks } from "@storyteller/components/src/constants/ThirdPartyLinks"
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/pagination";
|
||||
@@ -207,7 +208,7 @@ function IndexPage() {
|
||||
>
|
||||
<Tippy content="Discord" placement="right">
|
||||
<a
|
||||
href="https://discord.gg/fakeyou"
|
||||
href={ThirdPartyLinks.FAKEYOU_DISCORD}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
@@ -650,7 +651,7 @@ function IndexPage() {
|
||||
</p>
|
||||
<div>
|
||||
<a
|
||||
href="https://discord.gg/fakeyou"
|
||||
href={ThirdPartyLinks.FAKEYOU_DISCORD}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
className="btn btn-primary mt-4"
|
||||
@@ -746,7 +747,7 @@ function IndexPage() {
|
||||
</p>
|
||||
<div>
|
||||
<a
|
||||
href="https://discord.gg/fakeyou"
|
||||
href={ThirdPartyLinks.FAKEYOU_DISCORD}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
className="btn btn-primary mt-3"
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
MASTER_BRANCH='master'
|
||||
PRODUCTION_BRANCH='production-fakeyou'
|
||||
|
||||
git stash
|
||||
git checkout $MASTER_BRANCH
|
||||
git pull
|
||||
|
||||
if git show-ref --verify --quiet "refs/heads/$PRODUCTION_BRANCH"; then
|
||||
echo "Deleting branch ${PRODUCTION_BRANCH}"
|
||||
git branch -D $PRODUCTION_BRANCH
|
||||
fi
|
||||
|
||||
git checkout -b $PRODUCTION_BRANCH
|
||||
git push --set-upstream origin $PRODUCTION_BRANCH
|
||||
|
||||
git checkout $MASTER_BRANCH
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
MASTER_BRANCH='master'
|
||||
PRODUCTION_BRANCH='production-storyteller'
|
||||
|
||||
git stash
|
||||
git checkout $MASTER_BRANCH
|
||||
git pull
|
||||
|
||||
if git show-ref --verify --quiet "refs/heads/$PRODUCTION_BRANCH"; then
|
||||
echo "Deleting branch ${PRODUCTION_BRANCH}"
|
||||
git branch -D $PRODUCTION_BRANCH
|
||||
fi
|
||||
|
||||
git checkout -b $PRODUCTION_BRANCH
|
||||
git push --set-upstream origin $PRODUCTION_BRANCH
|
||||
|
||||
git checkout $MASTER_BRANCH
|
||||
|
||||
Reference in new issue
Block a user