import { client, urlFor } from '../client';
import { Link, useNavigate } from 'react-router-dom';
import { v4 as uuidv4 } from 'uuid';
import { MdDownloadForOffline } from 'react-icons/md';
import { AiTwotoneDelete } from 'react-icons/ai';
import { BsFillArrowUpRightCircleFill } from 'react-icons/bs';
import { fetchUser } from '../utils/fetchUser';
const Pin = ({ pin: { postedBy, image, _id, destination, save } }) => {
// console.log(postedBy);
const [postHovered, setPostHovered] = useState(false);
const navigate = useNavigate();
const user = fetchUser();
const alreadySaved = !!save?.filter((item) => item.postedBy._id === user.sub)
?.length;
const savePin = (id) => {
if (!alreadySaved) {
client
.patch(id)
.setIfMissing({ save: [] })
.insert('after', 'save[-1]', [
{
_key: uuidv4(),
userId: user.sub,
postedBy: {
_type: 'postedBy',
_ref: user.sub,
},
},
])
.commit()
.then(() => {
window.location.reload();
});
}
};
const deletePin = (id) => {
client.delete(id).then(() => {
window.location.reload();
});
};
return (
<div className='m-2'>
<div
onMouseEnter={() => setPostHovered(true)}
onMouseLeave={() => setPostHovered(false)}
onClick={() => navigate(`/pin-detail/${_id}`)}
className='relative cursor-zoom-in w-auto hover:shadow-lg rounded-lg overflow-hidden transition-all duration-500 ease-in-out'
>
<img
className='rounded-lg w-full'
src={urlFor(image).width(700).url()}
alt='user-post'
/>
{postHovered && (
<div
className='absolute top-0 w-full h-full flex flex-col justify-between p-1 pr-2 pt-2 pb-2 z-50'
style={{ height: '100%' }}
>
<div className='flex items-center justify-between'>
<div className='flex gap-2'>
<a
href={`${image?.asset?.url}`}
download
onClick={(e) => e.stopPropagation()}
className='bg-white w-9 h-9 rounded-full flex items-center justify-center text-dark text-xl opacity-75 hover:shadow-md outline-none'
>
<MdDownloadForOffline />
</a>
{alreadySaved ? (
<button className='bg-red-500 opacity-70 hover:opacity-100 text-white font-bold px-5 py-1 text-base rounded-3xl hover:shadow-md outlined-none'>
{save?.length} Saved
</button>
) : (
<button
onClick={(e) => {
e.stopPropagation();
savePin(_id);
}}
type='button'
className='bg-red-500 opacity-70 hover:opacity-100 text-white font-bold px-5 py-1 text-base rounded-3xl hover:shadow-md outlined-none'
>
Save
</button>
)}
</div>
<div className='flex justify-between items-center gap-2 w-full'>
{destination && (
<a
href={destination}
target='_blank'
rel='noreferrer'
className='bg-white flex items-center gap-2 text-black font-bold p-2 pl-4 pr-4 rounded-full opacity-70 hover:opacity-100 hover:shadow-md'
onClick={(e) => e.stopPropagation()}
>
<BsFillArrowUpRightCircleFill />
{destination.length > 15
? destination.slice(0, 15)
: destination}
</a>
)}
{postedBy?._id === user.sub && (
<button
onClick={(e) => {
e.stopPropagation();
deletePin(_id);
}}
className='bottom-0 bg-white w-9 h-9 rounded-full flex items-center justify-center text-dark text-xl opacity-75 hover:opacity-100 hover:shadow-md outline-none'
>
<AiTwotoneDelete />
</button>
)}
</div>
</div>
</div>
)}
</div>
<Link
to={`user-profile/${user?.sub}`}
className='flex gap-2 mt-2 items-center'
>
<img
className='w-8 h-8 rounded-full object-cover'
src={postedBy?.image}
alt='user-profile'
/>
<p className='font-semibold capitalize'>{postedBy.usernName}</p>
</Link>
{console.log(alreadySaved)}
</div>
);
};
export default Pin;
Here in the code, a user will save the pin (or post like snap) then button would be updated with saved button, but it is taking too much time to get updated , but when I save the pin sanity updates the pin's save array with the userId who have saved the pin (I can clearly see it on sanity studio in realtime).
Related
I'm building a dynamic Alert UI with tailwindcss. I want to make this alert so that it can be used anywhere when making all requests. I also combine the Alert with the value in the global state. Overall it works well, however there are the following caveats:
this the global state of alert used redux toolki:
import { createSlice } from '#reduxjs/toolkit';
const uiSlice = createSlice({
name: 'UI',
initialState: {
offcanvasVisible: false,
alert: {
isShow: false,
variant: '',
message: '',
},
},
reducers: {
offcanvasToggle: (state) => {
state.offcanvasVisible = !state.offcanvasVisible;
},
showAlert: (state, action) => {
state.alert.isShow = true;
state.alert.message = action.payload.message;
state.alert.variant = action.payload.variant;
},
closeAlert: (state) => {
state.alert.isShow = false;
state.alert.message = '';
state.alert.variant = '';
},
},
});
export const { offcanvasToggle, showAlert, closeAlert } = uiSlice.actions;
export default uiSlice.reducer;
the following code in the Alert component:
import { BiNoEntry } from 'react-icons/bi';
import { IoCloseSharp } from 'react-icons/io5';
import { BsCheckCircleFill, BsFillInfoCircleFill } from 'react-icons/bs';
import { useDispatch, useSelector } from 'react-redux';
import { closeAlert } from '../../store/ui-slice';
import { useEffect, useState } from 'react';
const Alert = () => {
const [showState, setShowState] = useState(false);
const [variantState, setVariantState] = useState('');
const [messageState, setMessageState] = useState('');
const [classes, setClasses] = useState('');
const dispatch = useDispatch();
const { variant, message, isShow } = useSelector((state) => state.ui.alert);
useEffect(() => {
if (!isShow) return;
setShowState(isShow);
setVariantState(variant);
setMessageState(message);
if (variant === 'info') {
setClasses('bg-blue-400 border-blue-500');
}
if (variant === 'success') {
setClasses('bg-green-400 border-green-500');
}
if (variant === 'failed') {
setClasses('bg-red-400 border-red-500');
}
}, [isShow, variant, message]);
return (
<div
className={`${
!showState && 'hidden'
} container-custom w-full py-4 text-white flex flex-row gap-2 items-center justify-between ${classes}`}
>
<div className="flex flex-row gap-2 items-center">
<span>
{variantState === 'info' ? (
<BsFillInfoCircleFill size="24" />
) : variantState === 'success' ? (
<BsCheckCircleFill size="24" />
) : variantState === 'failed' ? (
<BiNoEntry size="24" />
) : (
''
)}
</span>
{messageState}
</div>
<button
onClick={() => {
dispatch(closeAlert());
}}
className="text-white"
>
<IoCloseSharp color="white" size="24" />
</button>
</div>
);
};
export default Alert;
Code in the Contact Component:
/* eslint-disable jsx-a11y/iframe-has-title */
import LandingLayout from '../../components/Layout/LandingLayout';
import Breadcrumb from '../../components/UI/Breadcrumb';
import Input from '../../components/Form/Input';
import TextArea from '../../components/Form/TextArea';
import { AiOutlineWhatsApp } from 'react-icons/ai';
import { BsInstagram } from 'react-icons/bs';
import {
MdOutlineAddLocationAlt,
MdOutlineEmail,
MdPhone,
} from 'react-icons/md';
import Button from '../../components/UI/Button';
import { useForm } from 'react-hook-form';
import { useDispatch, useSelector } from 'react-redux';
import {
sendingEmail,
turnOffLoadingSendEmail,
turnOnLoadingSendEmail,
} from '../../store/landing-slice';
import Spin from '../../components/UI/Spin';
import { showAlert } from '../../store/ui-slice';
const Kontak = () => {
const dispatch = useDispatch();
const { loading, success, error } = useSelector(
(state) => state.landing.sendEmail
);
const {
register,
handleSubmit,
formState: { errors, isValid },
reset,
} = useForm({
mode: 'all',
});
if (success !== null) {
dispatch(showAlert({ variant: 'success', message: success.message }));
}
const onSubmit = (data) => {
if (!isValid) return;
dispatch(turnOnLoadingSendEmail());
setTimeout(() => {
dispatch(sendingEmail(data));
dispatch(turnOffLoadingSendEmail());
reset();
}, 1000);
};
return (
<LandingLayout>
<Breadcrumb title="Kontak Kami" />
<section className="container-custom py-10 w-full h-[450px] overflow-hidden">
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3960.687915874803!2d107.64700641530236!3d-6.927857094994467!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2e68e80a699f1971%3A0xca4c51951a56650c!2sPanti%20Asuhan%20Al-Hidayah!5e0!3m2!1sid!2sid!4v1666696368586!5m2!1sid!2sid"
width="100%"
height="100%"
style={{ border: 0 }}
allowFullScreen=""
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
/>
</section>
<section className="container-custom py-4 w-full flex flex-col gap-10 md:flex-row-reverse">
<form onSubmit={handleSubmit(onSubmit)} className="w-full">
<h2 className="text-gray-700 text-lg mb-3">
Hubungi kami melalui form dibawah ini.
</h2>
<div className="flex flex-col lg:flex-row lg:gap-3">
<Input
options={{
...register('name', {
required: 'Nama Lengkap tidak boleh kosong',
}),
}}
id="name"
label="Nama Lengkap"
requireIcon="true"
hasError={!!errors?.name}
errorMessage={errors?.name?.message}
/>
<Input
options={{
...register('email', {
required: 'E-Mail tidak boleh kosong',
pattern: {
value: /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email address',
},
}),
}}
id="email"
label="E-Mail"
requireIcon="true"
hasError={!!errors?.email}
errorMessage={errors?.email?.message}
/>
</div>
<Input
options={{
...register('subject', { required: 'Subjek tidak boleh kosong' }),
}}
id="subject"
label="Subjek"
requireIcon="true"
hasError={!!errors?.subject}
errorMessage={errors?.subject?.message}
/>
<TextArea
id="keterangan"
label="Keterangan"
options={{
...register('keterangan'),
rows: '4',
}}
></TextArea>
<Button
className="flex gap-2"
options={{
type: 'submit',
disabled: !isValid,
}}
>
{loading && <Spin />}
Kirim
</Button>
</form>
<div className="w-full flex flex-col gap-2 md:gap-4">
<div className="flex flex-row gap-2">
<span className="self-start rounded-full bg-gray-300 text-gray-700 p-3">
<MdOutlineAddLocationAlt size={32} />
</span>
<div className="flex flex-col gap-1">
<h3 className="pt-5 font-semibold text-[17px]">Alamat</h3>
Jl. Trs. St. Kiaracondong, RT.02/RW.08, Kebun Jayanti, Kec.
Kiaracondong, Kota Bandung, Jawa Barat 40281
</div>
</div>
<div className="flex flex-row gap-2">
<span className="self-start rounded-full bg-gray-300 text-gray-700 p-3">
<MdPhone size={32} />
</span>
<div className="flex flex-col gap-1">
<h3 className="pt-5 font-semibold text-[17px]">Telepon</h3>
022 7333116
</div>
</div>
<div className="flex flex-row gap-2">
<span className="self-start rounded-full bg-gray-300 text-gray-700 p-3">
<AiOutlineWhatsApp size={32} />
</span>
<div className="flex flex-col gap-1">
<h3 className="pt-5 font-semibold text-[17px]">Whatsapp</h3>
0882 43556 7721
</div>
</div>
<div className="flex flex-row gap-2">
<span className="self-start rounded-full bg-gray-300 text-gray-700 p-3">
<BsInstagram size={32} />
</span>
<div className="flex flex-col gap-1">
<h3 className="pt-5 font-semibold text-[17px]">Instagram</h3>
yayasan_alhidayah
</div>
</div>
<div className="flex flex-row gap-2">
<span className="self-start rounded-full bg-gray-300 text-gray-700 p-3">
<MdOutlineEmail size={32} />
</span>
<div className="flex flex-col gap-1">
<h3 className="pt-5 font-semibold text-[17px]">E-Mail</h3>
alhidayahkircon#gmail.com
</div>
</div>
</div>
</section>
</LandingLayout>
);
};
export default Kontak;
I put the Alert on the Navbar:
import { Link } from 'react-router-dom';
import Logo from '../UI/Logo';
import { FiMenu, FiPhone } from 'react-icons/fi';
import Button from '../UI/Button';
import Dropdown from '../UI/Dropdown';
import { BiDonateHeart } from 'react-icons/bi';
import { AiOutlineHistory } from 'react-icons/ai';
import Alert from '../UI/Alert';
const Navbar = (props) => {
return (
<header className="sticky top-0 z-10 bg-white border-b-2 border-slate-200">
<Alert />
<div className="container-custom flex justify-between items-center">
<div className="w-36 pl-2 md:pl-0">
<Link to="/">
<Logo />
</Link>
</div>
<ul className="hidden lg:flex font-medium">
<li className="mr-1 hover:bg-gray-100 py-2 px-4 rounded-md">
<Link to={'/'}>Beranda</Link>
</li>
<li className="mr-1 hover:bg-gray-100 py-2 px-4 rounded-md">
<Link to={'/kegiatan'}>Kegiatan</Link>
</li>
<li className="mr-1 hover:bg-gray-100 py-2 px-4 rounded-md">
<Dropdown label="Donasi">
<li className="block px-4 py-2 hover:bg-gray-100">
<Link className="flex items-center" to="/donasi">
<span className="mr-3">
<BiDonateHeart />
</span>
Formulir Donasi
</Link>
</li>
<li className="block px-4 py-2 hover:bg-gray-100">
<Link className="flex items-center" to="/cek-donasi">
<span className="mr-3">
<AiOutlineHistory />
</span>
<p>Cek Donasi</p>
</Link>
</li>
</Dropdown>
</li>
<li className="mr-1 hover:bg-gray-100 py-2 px-4 rounded-md">
<Dropdown label="Tentang Kami">
<li className="block px-4 py-2 hover:bg-gray-100">
<Link to="/profil-lembaga">Profil Lembaga</Link>
</li>
<li className="block px-4 py-2 hover:bg-gray-100">
<Link to="/visi-misi">Visi & Misi</Link>
</li>
<li className="block px-4 py-2 hover:bg-gray-100">
<Link to="/galeri">
<p>Galeri</p>
</Link>
</li>
<li className="block px-4 py-2 hover:bg-gray-100">
<Link to="/kontak">Hubungi Kami</Link>
</li>
</Dropdown>
</li>
</ul>
<div className="hidden lg:grid grid-cols-2 gap-1">
<a
className="flex items-center mr-1 hover:bg-gray-100 py-2 px-4 rounded-md"
href="tel:+0222334645"
>
<FiPhone size={'24'} />
<span className="ml-2">022 2334645</span>
</a>
<Button
className="flex justify-center items-center"
options={{
type: 'link',
href: '/donasi',
}}
>
Donasi
<span className="ml-1">
<BiDonateHeart />
</span>
</Button>
</div>
<div className="lg:hidden" onClick={props.offcanvasToggle}>
<FiMenu size={32} />
</div>
</div>
</header>
);
};
export default Navbar;
and here's what the UI should look like:
First of all, I thank you for being petrified
I hope I can use the best way to implement Alert without Warning like this.
This occurs because your updates to state are cascading. Your first set is causing the DOM to update. The update isn't yet complete, so your second (or third) set is also trying to update the DOM, and causing conflict.
I have found that when I have situations where I'm updating multiple state variables simultaneously it is better to use useReducer instead of useState. You can then setup a dispatch method that updates your multiple bits of state simultaneously, and even provide multiple methods that do different updates conditionally. This way state updates are concurrent, and avoid the errors you are seeing.
I first time build carousel with tailwind css and react slick.
Post component has own height calc(100vh - 65px). after I use flexbox like this:
data.map((i, index) => (
<div
key={index}
className="min-h-screen h-direct w-full mb-5 flex flex-col items-center p-2 md:px-6"
ref={index === data.length - 1 ? lastBookElementRef : null}
>
<Post
index={index}
item={i}
isModal={false}
t={t}
user={user}
/>
</div>
))
on Post page:
/* eslint-disable jsx-a11y/media-has-caption */
/* eslint-disable jsx-a11y/img-redundant-alt */
/* eslint-disable react/jsx-props-no-multi-spaces */
/* eslint-disable no-unused-expressions */
import { ArrowLeftOutlined, ArrowRightOutlined, DeleteOutlined, FileSyncOutlined, HeartFilled, HeartOutlined, LikeOutlined, UserOutlined } from '#ant-design/icons';
import axios from 'axios';
import Link from 'next/link';
import React, { useEffect, useRef, useState } from 'react';
import ReactPlayer from 'react-player';
import Slider from 'react-slick';
import { BallTriangle } from 'react-loader-spinner';
import calculateTime from '../calculateTime';
import { useSocket } from '../context/socket';
import Modal from './Modal';
const Post = ({ item, isModal, user, t }) => {
const [myLikes, setMyLikes] = useState(item.likes);
const [isOpen, setIsOpen] = useState(false);
const socket = useSocket();
const parentRef = useRef(null);
const scrollRef = useRef(null);
const [imageLoading, setImageLoading] = useState(item.files);
const imageLoaded = (id) => {
setImageLoading((prev) => prev.filter((t) => t.path !== id));
};
const [currentItem, setCurrentItem] = useState(0);
const settings = {
dots: false,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
initialSlide: 0,
swipeToSlide: true,
autoplay: false,
arrows: false,
lazyLoad: true,
};
const slide = useRef();
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
const addOrRemoveLike = async (postId) => {
const isLike = myLikes.filter((like) => like.user._id === user._id).length > 0;
try {
await axios({
method: 'PUT',
url: `${process.env.SERVER}/posts/${isLike ? 'unliked' : 'liked'}/${postId}`,
withCredentials: true,
});
if (!isLike) {
const users = [item.user._id];
if (item.user._id !== user._id) socket.emit('newnotification', { users });
}
isLike ? setMyLikes((prev) => prev.filter((p) => p.user._id !== user._id)) : setMyLikes((prev) => ([{ user }, ...prev]));
} catch (error) {
console.error(error);
}
};
const handleScroll = (direction) => {
const { current } = scrollRef;
setCurrentItem((prev) => (prev + 1) % (item.files.length));
const scrollAmount = current.offsetWidth ? current.offsetWidth : 100;
if (direction === 'left') {
current.scrollLeft -= scrollAmount;
} else {
current.scrollLeft += scrollAmount;
}
};
return (
<div className={`flex-1 w-[60%] border-b gap-3
md:w-full ${isModal ? 'flex-row' : 'flex-col'} flex items-center justify-start`}
>
{/* Top */}
<div className=" flex w-full flex-row items-center gap-3 ">
<div className="border overflow-hidden rounded-full w-14 h-14 sm:w-10 sm:h-10 flex items-center justify-center bg-nft-gray-2 dark:bg-nft-gray-3">
{
item.user.logo.length > 0
? <img src={`${process.env.SERVER}/${item.user.logo}`} className="w-full h-full object-cover" />
: <UserOutlined className="text-2xl sm:text-xl" />
}
</div>
<div className="flex-1 flex flex-row items-center justify-between gap-2">
<div className="flex-1 flex flex-col justify-start ">
<Link href={`user/${item.user.username}`}>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a className="text-blue-600 dark:text-blue-500 font-medium"> {item.user.username} </a>
</Link>
<div className="w-10/12">
<h1 className="text-xs font-thin">
{item.location}
</h1>
<h1 className="text-xs font-thin">
<span className="font-medium">posted: </span>
{calculateTime(item.date, t)}
</h1>
</div>
</div>
{
user._id === item.user._id && (
<div>
<DeleteOutlined className="transition-all duration-500 text-xl cursor-pointer hover:text-nft-black-1 dark:hover:text-nft-gray-1" />
</div>
)
}
</div>
</div>
{/* Bottom */}
<div className="flex-1 w-full flex flex-col gap-1">
{/* FIles */}
<div
className="flex-2 w-full border bg-nft-gray-1 dark:bg-nft-black-1
relative
"
>
<ArrowLeftOutlined
className="absolute top-1/2 -translate-y-1/2 left-0 -translate-x-1/2
bg-nft-black-1 text-white text-xl flex justify-center items-center p-1 rounded-full cursor-pointer
hover:text-2xl transition-all duration-500
dark:bg-nft-gray-3 z-[5]
"
onClick={() => slide.current.slickPrev()}
/>
<ArrowRightOutlined
className="absolute top-1/2 -translate-y-1/2 right-0 translate-x-1/2
bg-nft-black-1 text-white text-xl flex justify-center items-center p-1 rounded-full cursor-pointer
hover:text-2xl transition-all duration-500
dark:bg-nft-gray-3 z-[5]
"
onClick={() => slide.current.slickNext()}
/>
<div className="h-[70%] w-full border bg-nft-gray-1 dark:bg-nft-black-1">
<div className="relative h-full">
<Slider {...settings} ref={slide}>
{
item.files.map((i, item) => (
<div key={item} className="w-full h-full flex justify-center items-center ">
{
i.type.startsWith('image')
? (
<img
onLoad={() => imageLoaded(i.path)}
src={`${process.env.SERVER}/${i.path}`}
className={` object-cover
${imageLoading.filter((t) => t.path === i.path).length > 0 && 'hidden'}
`}
alt="image"
/>
)
: (
<video
src={`${process.env.SERVER}/${i.path}`}
className="object-contain"
controls
controlsList="nodownload"
/>
)
}
</div>
))
}
</Slider>
</div>
</div>
</div>
{/* Statistics */}
<div className="flex-1 flex flex-col gap-2">
<div className="flex w-full flex-row justify-between items-center">
<div className=" flex flex-row gap-3 transition-all duration-500 items-center justify-start relative">
{
myLikes.filter((like) => like.user._id === user._id).length > 0
? (
<HeartFilled
className={`text-2xl text-red-500 dark:text-red-600 ${user.id === item.user._id && 'cursor-pointer'}`}
onClick={() => addOrRemoveLike(item._id)}
/>
)
: (
<HeartOutlined
className="text-2xl"
onClick={() => addOrRemoveLike(item._id)}
/>
)
}
{
myLikes.length > 0 && (
<div
className={`text-sm font-medium ${user._id === item.user._id && 'cursor-pointer '}`}
onClick={() => { setIsOpen(user._id === item.user._id); }}
>
{numberWithCommas(myLikes.length).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')} likes
</div>
)
}
</div>
<div className="text-sm relative before:content-[''] before:absolute before:block before:w-full before:h-[2px]
before:bottom-0 before:left-0 before:bg-blue-500 dark:before:bg-blue-600
before:hover:scale-x-100 before:scale-x-0 before:origin-top-left
before:transition-all before:ease-in-out before:duration-300
cursor-pointer text-blue-500 dark:text-blue-600
"
>Go to this post
</div>
</div>
<div className="flex flex-row gap-2 flex-wrap">
{item.tags.map((i) => (
<div key={i} className="flex flex-row items-center bg-blue-500 px-1 text-white dark:bg-blue-600 transition-all duration-500">
<p className="text-sm ">#{i}</p>
</div>
))}
</div>
<div className="flex flex-row">
<p className="text-sm truncate whitespace-nowrap overflow-hidden">{item.description}</p>
</div>
</div>
</div>
{
isOpen && (
<Modal
setClose={setIsOpen}
header="Likes"
>
{
myLikes.map((i, index) => (
<div
key={index}
className={` ${index !== myLikes.length - 1 && 'mb-2'} flex flex-row justify-center items-center cursor-pointer`}
>
<div className="w-full flex-1 p-2 shadow dark:bg-nft-black-2 flex flex-row items-center justify-between">
<div className="flex flex-row gap-5 flex-1 items-center">
<div className="h-10 w-10 rounded-full flex justify-center items-center bg-nft-gray-1 dark:bg-nft-black-1 overflow-hidden">
{
i.user.logo.length > 0
? (
<img
src={`${process.env.SERVER}/${i.user.logo}`}
className="w-full h-full object-cover"
/>
)
: <UserOutlined />
}
</div>
<div className="flex flex-col items-start justify-between">
<p>{i.user.username}</p>
{((i.user.firstName.length > 0) || (i.user.lastName.length > 0))
&& (
<h1
className="font-medium text-sm"
>{`${i.user.firstName} ${i.user.lastName}`}
</h1>
)}
</div>
</div>
</div>
</div>
))
}
</Modal>
)
}
</div>
);
};
export default Post;
I use react-slick for carousel. But height dont get. I parent div heigth h-[70%]. When I see without images everything good but after add react slick carousel images height increased. How do this?
I'm trying to implement google authentication on my nextjs app. i'm implementing this in my layout component,
when i use user = null and isLoadingUser the button is loading fine but when changed to user = session?.user and isloadingUser = 'loading' the button is set being set to disabled?
Layout coponent code:
import { Fragment, useState } from 'react';
import { useRouter } from 'next/router';
import Head from 'next/head';
import Link from 'next/link';
import Image from 'next/image';
import PropTypes from 'prop-types';
import AuthModal from './AuthModal';
import { Menu, Transition } from '#headlessui/react';
import {
HeartIcon,
HomeIcon,
LogoutIcon,
PlusIcon,
SparklesIcon,
UserIcon,
} from '#heroicons/react/outline';
import { ChevronDownIcon } from '#heroicons/react/solid';
import { useSession, signOut } from 'next-auth/react';
const menuItems = [
{
label: 'List a new home',
icon: PlusIcon,
href: '/list',
},
{
label: 'My homes',
icon: HomeIcon,
href: '/homes',
},
{
label: 'Favorites',
icon: HeartIcon,
href: '/favorites',
},
{
label: 'Logout',
icon: LogoutIcon,
onClick: signOut,
},
];
const Layout = ({ children = null }) => {
const router = useRouter();
const { data: session, status } = useSession()
const [showModal, setShowModal] = useState(false);
const user = session?.user
const isLoadingUser = 'loading';
const openModal = () => setShowModal(true);
const closeModal = () => setShowModal(false);
return (
<>
<Head>
<title>SupaVacation | The Modern Dev</title>
<meta
name="title"
content="Learn how to Build a Fullstack App with Next.js, PlanetScale & Prisma | The Modern Dev"
/>
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="min-h-screen flex flex-col">
<header className="h-16 w-full shadow-md">
<div className="h-full container mx-auto">
<div className="h-full px-4 flex justify-between items-center space-x-4">
<Link href="/">
<a className="flex items-center space-x-1">
<SparklesIcon className="shrink-0 w-8 h-8 text-rose-500" />
<span className="text-xl font-semibold tracking-wide">
Supa<span className="text-rose-600">Vacation</span>
</span>
</a>
</Link>
<div className="flex items-center space-x-4">
<Link href="/create">
<a className="hidden sm:block hover:bg-gray-200 transition px-3 py-1 rounded-md">
List your home
</a>
</Link>
{isLoadingUser ? (
<div className="h-8 w-[75px] bg-gray-200 animate-pulse rounded-md" />
) : user ? (
<Menu as="div" className="relative z-50">
<Menu.Button className="flex items-center space-x-px group">
<div className="shrink-0 flex items-center justify-center rounded-full overflow-hidden relative bg-gray-200 w-9 h-9">
{user?.image ? (
<Image
src={user?.image}
alt={user?.name || 'Avatar'}
layout="fill"
/>
) : (
<UserIcon className="text-gray-400 w-6 h-6" />
)}
</div>
<ChevronDownIcon className="w-5 h-5 shrink-0 text-gray-500 group-hover:text-current" />
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 w-72 overflow-hidden mt-1 divide-y divide-gray-100 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="flex items-center space-x-2 py-4 px-4 mb-2">
<div className="shrink-0 flex items-center justify-center rounded-full overflow-hidden relative bg-gray-200 w-9 h-9">
{user?.image ? (
<Image
src={user?.image}
alt={user?.name || 'Avatar'}
layout="fill"
/>
) : (
<UserIcon className="text-gray-400 w-6 h-6" />
)}
</div>
<div className="flex flex-col truncate">
<span>{user?.name}</span>
<span className="text-sm text-gray-500">
{user?.email}
</span>
</div>
</div>
<div className="py-2">
{menuItems.map(
({ label, href, onClick, icon: Icon }) => (
<div
key={label}
className="px-2 last:border-t last:pt-2 last:mt-2"
>
<Menu.Item>
{href ? (
<Link href={href}>
<a className="flex items-center space-x-2 py-2 px-4 rounded-md hover:bg-gray-100">
<Icon className="w-5 h-5 shrink-0 text-gray-500" />
<span>{label}</span>
</a>
</Link>
) : (
<button
className="w-full flex items-center space-x-2 py-2 px-4 rounded-md hover:bg-gray-100"
onClick={onClick}
>
<Icon className="w-5 h-5 shrink-0 text-gray-500" />
<span>{label}</span>
</button>
)}
</Menu.Item>
</div>
)
)}
</div>
</Menu.Items>
</Transition>
</Menu>
) : (
<button
type="button"
onClick={openModal}
className="ml-4 px-4 py-1 rounded-md bg-rose-600 hover:bg-rose-500 focus:outline-none focus:ring-4 focus:ring-rose-500 focus:ring-opacity-50 text-white transition"
>
Log in
</button>
)}
</div>
</div>
</div>
</header>
<main className="flex-grow container mx-auto">
<div className="px-4 py-12">
{typeof children === 'function' ? children(openModal) : children}
</div>
</main>
<AuthModal show={showModal} onClose={closeModal} />
</div>
</>
);
};
Layout.propTypes = {
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
};
export default Layout;
I am new to Ract and building a multi step form in Next.js, where I also use Context. So my project structure is pretty wild and I don't get how / where to change the steps.status when moving to next step in the stepper. So far I have my context, managing half of states, basically the formData, but also the 'original' state of my stepper:
import { useState, createContext, useContext } from "react";
export const FormContext = createContext();
export default function FormProvider({ children }) {
const [data, setData] = useState({});
const [steps, setSteps] = useState([
{ name: 'Vertrag', status: 'current' },
{ name: 'Dateneingabe', status: 'upcoming' },
{ name: 'Bestätigung', status: 'upcoming' },
]);
const setFormValues = (values) => {
setData((prevValues) => ({
...prevValues,
...values,
}));
};
return (
<FormContext.Provider value={{ steps, data, setFormValues }}>
{children}
</FormContext.Provider>
);
}
export const useFormData = () => useContext(FormContext);
In Stepper.js I therefore import my formData:
import { CheckIcon } from '#heroicons/react/solid'
import { useContext } from 'react'
import { FormContext } from "../context";
// status: 'complete', 'current', 'upcoming'
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Stepper() {
const formData = useContext(FormContext);
const steps = formData.steps
return (
<nav aria-label="Progress">
<div className="flex items-center flex-col">
<ol className="flex items-center sm:flex-col md:flex-row mx-auto mt-32 mb-8">
{steps.map((step, stepIdx) => (
<li key={step.name} className={classNames(stepIdx !== steps.length - 1 ? 'pr-16 sm:pr-32' : '', 'relative')}>
{step.status === 'complete' ? (
<>
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="h-0.5 w-full bg-yellow-500" />
</div>
<a
href="#"
className="relative w-8 h-8 flex items-center justify-center bg-yellow-500 rounded-full hover:bg-yellow-500"
>
<span className="h-9 flex flex-col items-center">
<span className="relative top-2 z-10 w-8 h-8 flex items-center justify-center rounded-full group-hover:bg-indigo-800">
<CheckIcon className="w-5 h-5 text-white" aria-hidden="true" />
</span>
<span className="text-xs font-semibold tracking-wide text-gray-600 mt-8">{step.name}</span>
</span>
</a>
</>
) : step.status === 'current' ? (
<>
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="h-0.5 w-full bg-gray-200" />
</div>
<a
href="#"
className="relative w-8 h-8 flex items-center justify-center bg-white border-2 border-yellow-500 rounded-full"
aria-current="step"
>
<span className="h-9 flex flex-col items-center">
<span className="z-10 w-8 h-8 flex items-center justify-center rounded-full group-hover:bg-indigo-800">
<span className="relative h-2.5 w-2.5 bg-yellow-500 rounded-full relative" style={{top: '0.8rem'}} />
</span>
<span className="text-xs font-semibold tracking-wide text-gray-600" style={{marginTop: '2.72rem'}}>{step.name}</span>
</span>
</a>
</>
) : (
<>
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="h-0.5 w-full bg-gray-200" />
</div>
<a
href="#"
className="group relative w-8 h-8 flex items-center justify-center bg-white border-2 border-gray-300 rounded-full hover:border-gray-400"
>
<span className="h-9 flex flex-col items-center">
<span className="z-10 w-8 h-8 flex items-center justify-center rounded-full">
<span className="relative h-2.5 w-2.5 bg-transparent rounded-full group-hover:bg-gray-300" style={{top: '0.8rem'}} />
</span>
<span className="text-xs font-semibold tracking-wide text-gray-600" style={{marginTop: '2.72rem'}}>{step.name}</span>
</span>
</a>
</>
)}
</li>
))}
</ol>
</div>
</nav>
)
Moreover I have index.js page, where all the components come together
import { useState } from "react";
import Head from "next/head";
import Stepper from '../components/Stepper'
import styles from "../styles/styles.module.scss";
import FormCard from "../components/FormCard";
import Navbar from "../components/Navbar";
import { CheckIcon } from '#heroicons/react/solid'
import {
PersonalInfo,
ConfirmPurchase,
ContractInfo,
} from "../components/Forms";
import FormCompleted from "../components/FormCompleted";
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
const App = () => {
const [formStep, setFormStep] = useState(0);
const nextFormStep = () => setFormStep((currentStep) => currentStep + 1);
const prevFormStep = () => setFormStep((currentStep) => currentStep - 1);
const [activeStep, setActiveStep] = useState(0);
const handleNextStep = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
const handlePrevoiusStep = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
return (
<div>
<Head>
<title>Next.js Multi Step Form</title>
</Head>
< Navbar />
< Stepper activeStep={activeStep} />
<div className={styles.container}>
<FormCard currentStep={formStep} prevFormStep={prevFormStep}>
{formStep >= 0 && (
<ContractInfo formStep={formStep} nextFormStep={nextFormStep} />
)}
{formStep >= 1 && (
<PersonalInfo formStep={formStep} nextFormStep={nextFormStep} />
)}
{formStep >= 2 && (
<ConfirmPurchase formStep={formStep} nextFormStep={nextFormStep} />
)}
{formStep > 2 && <FormCompleted />}
</FormCard>
</div>
<div className="mt-1 mb-5 sm:mt-8 sm:flex sm:justify-center lg:justify-center">
<div className="rounded-md shadow">
<a role="button" tabIndex={0}
onClick={ () => { prevFormStep(); handlePrevoiusStep() }}
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-yellow-500 hover:bg-yallow-600 md:py-4 md:text-lg md:px-10"
>
Back
</a>
</div>
<div className="mt-3 sm:mt-0 sm:ml-3">
<a
onClick={ () => { nextFormStep(); handleNextStep() }}
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-yellow-500 hover:bg-yallow-600 md:py-4 md:text-lg md:px-10"
>
Next
</a>
</div>
</div>
</div>
);
};
export default App;
As you see, Stepper is managed in three different files. But I am at least capable to change the activeStep index when clicking on Buttons, which already was a huge challenge for me. So now I also need the design to change. So that activeStep gets the step.status === 'current'. All stepps index < activeStep index should get step.status === 'complete' and logically all stepps index > activeStep index - step.status === 'upcoming'. Now I tried to handle this in index.js, but of course get back step is undefined, even though it is defined in Stepper.js through context.
I have a multi step form, which is a quiz. I already set up the context to be able to pass data.
import { useState, createContext, useContext } from "react";
export const QuizContext = createContext();
export default function QuizProvider({ children }) {
const [data, setData] = useState({});
const setQuizValues = (values) => {
setData((prevValues) => ({
...prevValues,
...values,
}));
};
return (
<QuizContext.Provider value={{ data, setQuizValues }}>
{children}
</QuizContext.Provider>
);
}
export const useQuizData = () => useContext(QuizContext);
So, my first step is selecting a card.
The code below:
import { Card } from "../../stories/Card";
import { useQuizData } from "../../context/index"
import { useState } from "react";
const tacos = [
{
cathegory: 'Meat',
imgURL: 'https://images.unsplash.com/photo-1560781290-7dc94c0f8f4f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3024&q=80'
},
{
cathegory: 'Fish',
imgURL: 'https://images.unsplash.com/photo-1510130387422-82bed34b37e9?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=975&q=80'
},
{
cathegory: 'Veggi',
imgURL: 'https://images.unsplash.com/photo-1572527129705-a6c197003d61?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=975&q=80'
},
]
export const TacoCathegories = ({quizStep, prevQuizStep, nextQuizStep}) => {
// const { setQuizValues } = useQuizData();
const [isSelected, setisSelected] = useState();
const handleSubmit = (values) => {
setQuizValues(values);
prevQuizStep();
nextQuizStep();
};
return (
<div className={quizStep === 0 ? 'block': 'hidden'}>
<div className="text-center">
<h2 className="text-3xl font-extrabold tracking-tight text-gray-600 sm:text-4xl">What is your favourite taco group?</h2>
</div>
<div className="max-w-7xl mx-auto py-24 px-4 sm:px-6 lg:px-8">
<div className="mt space-y-12 lg:space-y-0 lg:grid lg:grid-cols-3 lg:gap-x-8">
{tacos.map((taco, index) => (
<Card
role="button"
key={index}
title={taco.cathegory}
source={taco.imgURL}
text={`image of ${taco.cathegory} `}
selected={isSelected === index}
onChange={() => setisSelected(index)}
/>
))}
</div>
{tacos[isSelected] && <p>{tacos[isSelected].cathegory}</p>}
<div className="mt-5 sm:mt-8 sm:flex sm:justify-center lg:justify-center">
<div className="rounded-md shadow">
<a role="button" tabIndex={0}
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-gray-200 hover:bg-gray-200 focus:outline-none md:py-4 md:text-lg md:px-10 cursor-not-allowed"
>
Back
</a>
</div>
<div className="mt-3 sm:mt-0 sm:ml-3">
<a
onClick={nextQuizStep}
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-yellow-500 hover:bg-yallow-600 md:py-4 md:text-lg md:px-10"
>
Next
</a>
</div>
</div>
</div>
</div>
);
}
I am able to get the category of selected card tacos[isSelected].cathegory. Depending on the category I need to render different content in Step 2 of my multistep form. Basically, if I choose Meat I will render cards with Meat Tacos, If I choose Fish - with Fish Tacos. For now second step is empty, because I couldn't figure out how to pass selected category to second step.
export const TacoTypes = ({quizStep, prevQuizStep, nextQuizStep}) => {
return (
<div className={quizStep === 1 ? 'block' : 'hidden'}>
<div>
<p>Taco Types are: all you find in the object</p>
</div>
<div className="mt-5 sm:mt-8 sm:flex sm:justify-center lg:justify-center">
<div className="rounded-md shadow">
<a role="button" tabIndex={0}
onClick={prevQuizStep}
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-yellow-500 hover:bg-yallow-600 md:py-4 md:text-lg md:px-10"
>
Back
</a>
</div>
<div className="mt-3 sm:mt-0 sm:ml-3">
<a
onClick={nextQuizStep}
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-yellow-500 hover:bg-yallow-600 md:py-4 md:text-lg md:px-10"
>
Next
</a>
</div>
</div>
</div>
)
}
I am new to react, so any tipp would be helpful!
Sandbox: https://codesandbox.io/s/agitated-euler-pny5n