mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-01-04 06:54:27 +01:00
feat: build settings page for profile, re-calc navHeight on resize, move the navHeight from js to css var
This commit is contained in:
@@ -29,6 +29,7 @@ const DonatePage = Loadable(React.lazy(() => import("./features/Donations/pages/
|
||||
const LoginPage = Loadable(React.lazy(() => import("./features/Auth/pages/LoginPage/LoginPage")))
|
||||
const LogoutPage = Loadable(React.lazy(() => import("./features/Auth/pages/LogoutPage/LogoutPage")))
|
||||
const ProfilePage = Loadable(React.lazy(() => import("./features/Profiles/pages/ProfilePage/ProfilePage")))
|
||||
const EditProfilePage = Loadable(React.lazy(() => import("./features/Profiles/pages/EditProfilePage/EditProfilePage")))
|
||||
|
||||
|
||||
|
||||
@@ -104,7 +105,9 @@ function App() {
|
||||
|
||||
<Route path={PAGES_ROUTES.donate.default} element={<DonatePage />} />
|
||||
|
||||
<Route path={PAGES_ROUTES.profile.editProfile} element={<EditProfilePage />} />
|
||||
<Route path={PAGES_ROUTES.profile.byId} element={<ProfilePage />} />
|
||||
|
||||
<Route path={PAGES_ROUTES.auth.login} element={<LoginPage />} />
|
||||
<Route path={PAGES_ROUTES.auth.logout} element={<LogoutPage />} />
|
||||
|
||||
|
||||
@@ -178,6 +178,16 @@ export default function NavDesktop() {
|
||||
>
|
||||
👾 Profile
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
href="/edit-profile"
|
||||
onClick={(e) => {
|
||||
e.syntheticEvent.preventDefault();
|
||||
navigate("/edit-profile");
|
||||
}}
|
||||
className='!p-16 font-medium flex gap-16 hover:bg-gray-100 !rounded-12'
|
||||
>
|
||||
⚙️ Settings
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
href="/logout"
|
||||
onClick={(e) => {
|
||||
|
||||
@@ -105,6 +105,16 @@ export default function NavMobile() {
|
||||
>
|
||||
👾 Profile
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
href="/edit-profile"
|
||||
onClick={(e) => {
|
||||
e.syntheticEvent.preventDefault();
|
||||
navigate("/edit-profile");
|
||||
}}
|
||||
className='!p-16 font-medium flex gap-16 hover:bg-gray-100 !rounded-12'
|
||||
>
|
||||
⚙️ Settings
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
href="/logout"
|
||||
onClick={(e) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import NavMobile from "./NavMobile";
|
||||
import { MdComment, MdHomeFilled, MdLocalFireDepartment } from "react-icons/md";
|
||||
import { useEffect, } from "react";
|
||||
import { useAppDispatch, useMediaQuery } from "src/utils/hooks";
|
||||
import { useCallback, useEffect, } from "react";
|
||||
import { useAppDispatch, useMediaQuery, useResizeListener } from "src/utils/hooks";
|
||||
import { setNavHeight } from "src/redux/features/ui.slice";
|
||||
import NavDesktop from "./NavDesktop";
|
||||
import { MEDIA_QUERIES } from "src/utils/theme/media_queries";
|
||||
@@ -43,18 +43,24 @@ export default function Navbar() {
|
||||
|
||||
const isLargeScreen = useMediaQuery(MEDIA_QUERIES.isLarge)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const updateNavHeight = useCallback(() => {
|
||||
const nav = document.querySelector("nav");
|
||||
if (nav) {
|
||||
const navStyles = getComputedStyle(nav);
|
||||
if (navStyles.display !== "none") {
|
||||
dispatch(setNavHeight(nav.clientHeight));
|
||||
document.documentElement.style.setProperty('--navHeight', nav.clientHeight + 'px')
|
||||
}
|
||||
}
|
||||
|
||||
}, [dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
updateNavHeight();
|
||||
}, [updateNavHeight]);
|
||||
|
||||
useResizeListener(updateNavHeight)
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="sticky top-0 left-0 w-full z-[2010]">
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { useState } from 'react'
|
||||
import Button from 'src/Components/Button/Button'
|
||||
import { useGetHackathonsQuery } from 'src/graphql'
|
||||
import { useAppSelector } from 'src/utils/hooks'
|
||||
import HackathonsList from '../../Components/HackathonsList/HackathonsList'
|
||||
import SortByFilter from '../../Components/SortByFilter/SortByFilter'
|
||||
import styles from './styles.module.scss'
|
||||
@@ -21,9 +20,6 @@ export default function HackathonsPage() {
|
||||
tag: Number(tagFilter)
|
||||
},
|
||||
})
|
||||
const { navHeight } = useAppSelector((state) => ({
|
||||
navHeight: state.ui.navHeight
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -35,11 +31,7 @@ export default function HackathonsPage() {
|
||||
className={`page-container pt-16 w-full ${styles.grid}`}
|
||||
>
|
||||
<aside className='no-scrollbar'>
|
||||
<div className="sticky flex flex-col gap-24 md:overflow-y-scroll"
|
||||
style={{
|
||||
top: `${navHeight + 16}px`,
|
||||
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
|
||||
}}>
|
||||
<div className="flex flex-col gap-24 md:overflow-y-scroll sticky-side-element">
|
||||
<h1 id='title' className="text-body1 lg:text-h2 font-bolder">Hackathons 🏆</h1>
|
||||
<SortByFilter
|
||||
filterChanged={setSortByFilter}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useUpdateEffect } from '@react-hookz/web'
|
||||
import { useState } from 'react'
|
||||
import { useFeedQuery } from 'src/graphql'
|
||||
import { useAppSelector, useInfiniteQuery, usePreload } from 'src/utils/hooks'
|
||||
import { useInfiniteQuery, usePreload } from 'src/utils/hooks'
|
||||
import PostsList from '../../Components/PostsList/PostsList'
|
||||
import TrendingCard from '../../Components/TrendingCard/TrendingCard'
|
||||
import PopularTagsFilter, { FilterTag } from './PopularTagsFilter/PopularTagsFilter'
|
||||
@@ -34,10 +34,6 @@ export default function FeedPage() {
|
||||
|
||||
usePreload('PostPage');
|
||||
|
||||
const { navHeight, isLoggedIn } = useAppSelector((state) => ({
|
||||
navHeight: state.ui.navHeight,
|
||||
isLoggedIn: Boolean(state.user.me),
|
||||
}));
|
||||
|
||||
|
||||
return (
|
||||
@@ -76,11 +72,7 @@ export default function FeedPage() {
|
||||
/>
|
||||
</div>
|
||||
<aside id='categories' className='no-scrollbar'>
|
||||
<div className="sticky md:overflow-y-scroll"
|
||||
style={{
|
||||
top: `${navHeight + 16}px`,
|
||||
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
|
||||
}}>
|
||||
<div className="pb-16 md:overflow-y-scroll sticky-side-element">
|
||||
<Button
|
||||
href='/blog/create-post'
|
||||
color='primary'
|
||||
@@ -98,12 +90,7 @@ export default function FeedPage() {
|
||||
</div>
|
||||
</aside>
|
||||
<aside id='side' className='no-scrollbar'>
|
||||
<div className="sticky flex flex-col gap-24"
|
||||
style={{
|
||||
top: `${navHeight + 16}px`,
|
||||
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
|
||||
overflowY: "scroll",
|
||||
}}>
|
||||
<div className="pb-16 flex flex-col gap-24 overflow-y-auto sticky-side-element" >
|
||||
<TrendingCard />
|
||||
<div className='min-h-[300px] text-white flex flex-col justify-end p-24 rounded-12 relative overflow-hidden'
|
||||
style={{
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useParams } from 'react-router-dom'
|
||||
import NotFoundPage from 'src/features/Shared/pages/NotFoundPage/NotFoundPage'
|
||||
import { Post_Type, usePostDetailsQuery } from 'src/graphql'
|
||||
import { capitalize } from 'src/utils/helperFunctions'
|
||||
import { useAppSelector, } from 'src/utils/hooks'
|
||||
import { CommentsSection } from '../../Components/Comments'
|
||||
import ScrollToTop from 'src/utils/routing/scrollToTop'
|
||||
import TrendingCard from '../../Components/TrendingCard/TrendingCard'
|
||||
@@ -27,9 +26,6 @@ export default function PostDetailsPage() {
|
||||
skip: isNaN(Number(id)),
|
||||
})
|
||||
|
||||
const { navHeight } = useAppSelector((state) => ({
|
||||
navHeight: state.ui.navHeight
|
||||
}));
|
||||
|
||||
if (postDetailsQuery.loading)
|
||||
return <PostDetailsPageSkeleton />
|
||||
@@ -50,11 +46,7 @@ export default function PostDetailsPage() {
|
||||
className={`page-container grid pt-16 w-full gap-32 ${styles.grid}`}
|
||||
>
|
||||
<aside id='actions' className='no-scrollbar'>
|
||||
<div className="sticky"
|
||||
style={{
|
||||
top: `${navHeight + 16}px`,
|
||||
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
|
||||
}}>
|
||||
<div className="sticky-side-element">
|
||||
<PostActions post={post} />
|
||||
</div>
|
||||
</aside>
|
||||
@@ -62,12 +54,7 @@ export default function PostDetailsPage() {
|
||||
|
||||
<PageContent post={post} />
|
||||
<aside id='author' className='no-scrollbar min-w-0'>
|
||||
<div className="flex flex-col gap-24"
|
||||
style={{
|
||||
top: `${navHeight + 16}px`,
|
||||
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
|
||||
overflowY: "scroll",
|
||||
}}>
|
||||
<div className="flex flex-col gap-24 overflow-y-auto sticky-side-element">
|
||||
<AuthorCard author={post.author} />
|
||||
<TrendingCard />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { Navigate, NavLink, Route, Routes } from "react-router-dom";
|
||||
import LoadingPage from "src/Components/LoadingPage/LoadingPage";
|
||||
import NotFoundPage from "src/features/Shared/pages/NotFoundPage/NotFoundPage";
|
||||
import { useProfileQuery } from "src/graphql";
|
||||
import { useAppSelector } from "src/utils/hooks";
|
||||
import CommentsSettingsCard from "../ProfilePage/CommentsSettingsCard/CommentsSettingsCard";
|
||||
import UpdateMyProfileCard from "./UpdateMyProfileCard/UpdateMyProfileCard";
|
||||
import { Helmet } from 'react-helmet'
|
||||
|
||||
|
||||
const links = [
|
||||
{
|
||||
text: "👾 My Profile",
|
||||
path: 'my-profile',
|
||||
},
|
||||
{
|
||||
text: "⚙️ Preferences",
|
||||
path: 'preferences',
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
export default function EditProfilePage() {
|
||||
const userId = useAppSelector(state => state.user.me?.id)
|
||||
const profileQuery = useProfileQuery({
|
||||
variables: {
|
||||
profileId: userId!,
|
||||
},
|
||||
skip: !userId,
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
if (!userId || profileQuery.loading)
|
||||
return <LoadingPage />
|
||||
|
||||
if (!profileQuery.data?.profile)
|
||||
return <NotFoundPage />
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Settings</title>
|
||||
<meta property="og:title" content='Settings' />
|
||||
</Helmet>
|
||||
<div className="page-container grid grid-cols-1 md:grid-cols-4 gap-24">
|
||||
<aside>
|
||||
<div className='bg-white border-2 border-gray-200 rounded-12 p-16 sticky-side-element' >
|
||||
<p className="text-body2 font-bolder text-black mb-16">Edit maker profile</p>
|
||||
<ul className=' flex flex-col gap-8'>
|
||||
{links.map((link, idx) =>
|
||||
<li key={idx}>
|
||||
<NavLink
|
||||
to={link.path}
|
||||
className={({ isActive }) => `flex items-start rounded-8 cursor-pointer font-bold p-12
|
||||
active:scale-95 transition-transform
|
||||
${isActive ? 'bg-gray-100' : 'hover:bg-gray-50'}
|
||||
`}
|
||||
>
|
||||
{link.text}
|
||||
</NavLink>
|
||||
</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
<main className="md:col-span-2">
|
||||
<Routes>
|
||||
<Route index element={<Navigate to='my-profile' />} />
|
||||
<Route path='my-profile' element={<UpdateMyProfileCard data={profileQuery.data.profile} />} />
|
||||
<Route path='preferences' element={<CommentsSettingsCard nostr_prv_key={profileQuery.data.profile.nostr_prv_key} nostr_pub_key={profileQuery.data.profile.nostr_pub_key} isOwner={true} />
|
||||
} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
import { SubmitHandler, useForm } from "react-hook-form"
|
||||
import Button from "src/Components/Button/Button";
|
||||
import { User, useUpdateProfileAboutMutation } from "src/graphql";
|
||||
import { NotificationsService } from "src/services/notifications.service";
|
||||
import * as yup from "yup";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import Avatar from "src/features/Profiles/Components/Avatar/Avatar";
|
||||
|
||||
|
||||
|
||||
interface Props {
|
||||
data: Pick<User,
|
||||
| 'name'
|
||||
| 'email'
|
||||
| 'lightning_address'
|
||||
| 'jobTitle'
|
||||
| 'avatar'
|
||||
| 'website'
|
||||
| 'github'
|
||||
| 'twitter'
|
||||
| 'linkedin'
|
||||
| 'location'
|
||||
| 'bio'
|
||||
>,
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
type IFormInputs = Props['data'];
|
||||
|
||||
const schema: yup.SchemaOf<IFormInputs> = yup.object({
|
||||
name: yup.string().trim().required().min(2),
|
||||
avatar: yup.string().url().required(),
|
||||
bio: yup.string().ensure(),
|
||||
email: yup.string().email().ensure(),
|
||||
github: yup.string().ensure(),
|
||||
jobTitle: yup.string().ensure(),
|
||||
lightning_address: yup
|
||||
.string()
|
||||
.test({
|
||||
name: "is valid lightning_address",
|
||||
test: async value => {
|
||||
try {
|
||||
if (value) {
|
||||
const [name, domain] = value.split("@");
|
||||
const lnurl = `https://${domain}/.well-known/lnurlp/${name}`;
|
||||
const res = await fetch(lnurl);
|
||||
if (res.status === 200) return true;
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
})
|
||||
.ensure()
|
||||
.label("lightning address"),
|
||||
linkedin: yup.string().ensure(),
|
||||
location: yup.string().ensure(),
|
||||
twitter: yup.string().ensure(),
|
||||
website: yup.string().url().ensure(),
|
||||
|
||||
}).required();
|
||||
|
||||
export default function UpdateMyProfileCard({ data, onClose }: Props) {
|
||||
|
||||
const { register, formState: { errors }, handleSubmit } = useForm<IFormInputs>({
|
||||
defaultValues: data,
|
||||
resolver: yupResolver(schema),
|
||||
mode: 'onBlur',
|
||||
});
|
||||
|
||||
const [mutate, mutationStatus] = useUpdateProfileAboutMutation({
|
||||
onCompleted: () => {
|
||||
onClose?.()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
const onSubmit: SubmitHandler<IFormInputs> = data => {
|
||||
mutate({
|
||||
variables: {
|
||||
data: {
|
||||
name: data.name,
|
||||
avatar: data.avatar,
|
||||
jobTitle: data.jobTitle,
|
||||
bio: data.bio,
|
||||
email: data.email,
|
||||
github: data.github,
|
||||
linkedin: data.linkedin,
|
||||
lightning_address: data.lightning_address,
|
||||
location: data.location,
|
||||
twitter: data.twitter,
|
||||
website: data.website,
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
NotificationsService.error('A network error happened');
|
||||
mutationStatus.reset()
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-16 bg-white border-2 border-gray-200">
|
||||
<div className="bg-gray-600 relative h-[160px] rounded-t-16">
|
||||
<div className="absolute left-24 bottom-0 translate-y-1/2">
|
||||
<Avatar src={data.avatar} width={120} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-16 md:p-24 mt-64">
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<p className="text-body5 font-medium">
|
||||
Name
|
||||
</p>
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<input
|
||||
autoFocus
|
||||
type='text'
|
||||
className="input-text"
|
||||
placeholder='John Doe'
|
||||
{...register("name")}
|
||||
/>
|
||||
</div>
|
||||
{errors.name && <p className="input-error">
|
||||
{errors.name.message}
|
||||
</p>}
|
||||
<p className="text-body5 mt-16 font-medium">
|
||||
Avatar
|
||||
</p>
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<input
|
||||
|
||||
type='text'
|
||||
className="input-text"
|
||||
placeholder='https://images.com/my-avatar.jpg'
|
||||
{...register("avatar")}
|
||||
/>
|
||||
</div>
|
||||
{errors.avatar && <p className="input-error">
|
||||
{errors.avatar.message}
|
||||
</p>}
|
||||
<p className="text-body5 mt-16 font-medium">
|
||||
Bio
|
||||
</p>
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<textarea
|
||||
|
||||
rows={3}
|
||||
className="input-text !p-20"
|
||||
placeholder='Tell others a little bit about yourself'
|
||||
{...register("bio")}
|
||||
/>
|
||||
</div>
|
||||
{errors.bio && <p className="input-error">
|
||||
{errors.bio.message}
|
||||
</p>}
|
||||
<p className="text-body5 mt-16 font-medium">
|
||||
Job Title
|
||||
</p>
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<input
|
||||
|
||||
type='text'
|
||||
className="input-text"
|
||||
placeholder="Back-end Developer"
|
||||
{...register("jobTitle")}
|
||||
/>
|
||||
</div>
|
||||
{errors.jobTitle && <p className="input-error">
|
||||
{errors.jobTitle.message}
|
||||
</p>}
|
||||
<p className="text-body5 mt-16 font-medium">
|
||||
Location
|
||||
</p>
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<input
|
||||
|
||||
type='text'
|
||||
className="input-text"
|
||||
placeholder="UK, London"
|
||||
{...register("location")}
|
||||
/>
|
||||
</div>
|
||||
{errors.location && <p className="input-error">
|
||||
{errors.location.message}
|
||||
</p>}
|
||||
<p className="text-body5 mt-16 font-medium">
|
||||
Website
|
||||
</p>
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<input
|
||||
|
||||
type='text'
|
||||
className="input-text"
|
||||
placeholder="www.website.io"
|
||||
{...register("website")}
|
||||
/>
|
||||
</div>
|
||||
{errors.website && <p className="input-error">
|
||||
{errors.website.message}
|
||||
</p>}
|
||||
<p className="text-body5 mt-16 font-medium">
|
||||
Twitter
|
||||
</p>
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<input
|
||||
|
||||
type='text'
|
||||
className="input-text"
|
||||
placeholder="@johndoe"
|
||||
{...register("twitter")}
|
||||
/>
|
||||
</div>
|
||||
{errors.twitter && <p className="input-error">
|
||||
{errors.twitter.message}
|
||||
</p>}
|
||||
<p className="text-body5 mt-16 font-medium">
|
||||
Github
|
||||
</p>
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<input
|
||||
|
||||
type='text'
|
||||
className="input-text"
|
||||
placeholder="johndoe"
|
||||
{...register("github")}
|
||||
/>
|
||||
</div>
|
||||
{errors.github && <p className="input-error">
|
||||
{errors.github.message}
|
||||
</p>}
|
||||
<p className="text-body5 mt-16 font-medium">
|
||||
Linkedin
|
||||
</p>
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<input
|
||||
|
||||
type='text'
|
||||
className="input-text"
|
||||
placeholder="www.linkedin.com/in/john-doe"
|
||||
{...register("linkedin")}
|
||||
/>
|
||||
</div>
|
||||
{errors.linkedin && <p className="input-error">
|
||||
{errors.linkedin.message}
|
||||
</p>}
|
||||
<p className="text-body5 mt-16 font-medium">
|
||||
Lightning address
|
||||
</p>
|
||||
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<input
|
||||
|
||||
type='text'
|
||||
className="input-text"
|
||||
placeholder="johndoe@lnd.com"
|
||||
{...register("lightning_address")}
|
||||
/>
|
||||
</div>
|
||||
{errors.lightning_address && <p className="input-error">
|
||||
{errors.lightning_address.message}
|
||||
</p>}
|
||||
<p className="text-body6 text-gray-400 mt-8 max-w-[70ch]">
|
||||
Your lightning address is used to send the votes you get on your posts, comments, apps...etc, directly to you.
|
||||
</p>
|
||||
<div className="mt-24 flex gap-16 justify-end">
|
||||
<Button
|
||||
color='gray'
|
||||
disabled={mutationStatus.loading}
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type='submit'
|
||||
color='primary'
|
||||
isLoading={mutationStatus.loading}
|
||||
disabled={mutationStatus.loading}
|
||||
>
|
||||
Save changes
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
$screen-xs-min: 320px;
|
||||
|
||||
@import "./tw.scss", "./shared.scss", "./vendors.scss", "./scrollbar.scss";
|
||||
@import "./tw.scss", "./shared.scss", "./vendors.scss", "./scrollbar.scss",
|
||||
"./ui_state.scss";
|
||||
@import "/src/styles/mixins/index.scss";
|
||||
|
||||
html {
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
.input-removed-arrows::-webkit-outer-spin-button,
|
||||
.input-removed-arrows::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
.input-removed-arrows[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
button[disabled]{
|
||||
opacity: .5;
|
||||
pointer-events: none;
|
||||
}
|
||||
button[disabled] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sticky-side-element {
|
||||
position: sticky;
|
||||
top: calc(var(--navHeight) + 16px);
|
||||
max-height: calc(100vh - var(--navHeight) - 16px);
|
||||
}
|
||||
|
||||
3
src/styles/ui_state.scss
Normal file
3
src/styles/ui_state.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
:root {
|
||||
--navHeight: 0;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useDebouncedCallback } from "@react-hookz/web";
|
||||
import { useEffect } from "react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
|
||||
export const useResizeListener = (
|
||||
listener: () => void,
|
||||
@@ -7,7 +7,7 @@ export const useResizeListener = (
|
||||
) => {
|
||||
options.debounce = options.debounce ?? 250;
|
||||
|
||||
const func = useDebouncedCallback(listener, [], options.debounce)
|
||||
const func = useDebouncedCallback(listener, [listener], options.debounce)
|
||||
useEffect(() => {
|
||||
window.addEventListener("resize", func);
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ export const PAGES_ROUTES = {
|
||||
default: "/donate"
|
||||
},
|
||||
profile: {
|
||||
editProfile: "/edit-profile/*",
|
||||
byId: "/profile/:id/*",
|
||||
},
|
||||
auth: {
|
||||
|
||||
Reference in New Issue
Block a user