first working state
This commit is contained in:
		
							parent
							
								
									0a9b76f9c7
								
							
						
					
					
						commit
						4a0db151d4
					
				
					 23 changed files with 512 additions and 74 deletions
				
			
		
							
								
								
									
										129
									
								
								components/alert.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								components/alert.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,129 @@ | |||
| import { useState, useEffect, useRef } from "react"; | ||||
| import { useRouter } from "next/router"; | ||||
| import PropTypes from "prop-types"; | ||||
| 
 | ||||
| import { alertService, AlertType } from "../services"; | ||||
| 
 | ||||
| export { Alert }; | ||||
| 
 | ||||
| Alert.propTypes = { | ||||
|   id: PropTypes.string, | ||||
|   fade: PropTypes.bool, | ||||
| }; | ||||
| 
 | ||||
| Alert.defaultProps = { | ||||
|   id: "default-alert", | ||||
|   fade: true, | ||||
| }; | ||||
| 
 | ||||
| function Alert({ id, fade }) { | ||||
|   const mounted = useRef(false); | ||||
|   const router = useRouter(); | ||||
|   const [alerts, setAlerts] = useState([]); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     mounted.current = true; | ||||
| 
 | ||||
|     // subscribe to new alert notifications
 | ||||
|     const subscription = alertService.onAlert(id).subscribe((alert) => { | ||||
|       // clear alerts when an empty alert is received
 | ||||
|       if (!alert.message) { | ||||
|         setAlerts((alerts) => { | ||||
|           // filter out alerts without 'keepAfterRouteChange' flag
 | ||||
|           const filteredAlerts = alerts.filter((x) => x.keepAfterRouteChange); | ||||
| 
 | ||||
|           // remove 'keepAfterRouteChange' flag on the rest
 | ||||
|           return omit(filteredAlerts, "keepAfterRouteChange"); | ||||
|         }); | ||||
|       } else { | ||||
|         // add alert to array with unique id
 | ||||
|         alert.itemId = Math.random(); | ||||
|         setAlerts((alerts) => [...alerts, alert]); | ||||
| 
 | ||||
|         // auto close alert if required
 | ||||
|         if (alert.autoClose) { | ||||
|           setTimeout(() => removeAlert(alert), 3000); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     // clear alerts on location change
 | ||||
|     const clearAlerts = () => alertService.clear(id); | ||||
|     router.events.on("routeChangeStart", clearAlerts); | ||||
| 
 | ||||
|     // clean up function that runs when the component unmounts
 | ||||
|     return () => { | ||||
|       mounted.current = false; | ||||
| 
 | ||||
|       // unsubscribe to avoid memory leaks
 | ||||
|       subscription.unsubscribe(); | ||||
|       router.events.off("routeChangeStart", clearAlerts); | ||||
|     }; | ||||
| 
 | ||||
|     // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|   }, []); | ||||
| 
 | ||||
|   function omit(arr, key) { | ||||
|     return arr.map((obj) => { | ||||
|       const { [key]: omitted, ...rest } = obj; | ||||
|       return rest; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function removeAlert(alert) { | ||||
|     if (!mounted.current) return; | ||||
| 
 | ||||
|     if (fade) { | ||||
|       // fade out alert
 | ||||
|       setAlerts((alerts) => | ||||
|         alerts.map((x) => | ||||
|           x.itemId === alert.itemId ? { ...x, fade: true } : x | ||||
|         ) | ||||
|       ); | ||||
| 
 | ||||
|       // remove alert after faded out
 | ||||
|       setTimeout(() => { | ||||
|         setAlerts((alerts) => alerts.filter((x) => x.itemId !== alert.itemId)); | ||||
|       }, 250); | ||||
|     } else { | ||||
|       // remove alert
 | ||||
|       setAlerts((alerts) => alerts.filter((x) => x.itemId !== alert.itemId)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function cssClasses(alert) { | ||||
|     if (!alert) return; | ||||
| 
 | ||||
|     const classes = ["alert", "alert-dismissable"]; | ||||
| 
 | ||||
|     const alertTypeClass = { | ||||
|       [AlertType.Success]: "alert-success", | ||||
|       [AlertType.Error]: "alert-danger", | ||||
|       [AlertType.Info]: "alert-info", | ||||
|       [AlertType.Warning]: "alert-warning", | ||||
|     }; | ||||
| 
 | ||||
|     classes.push(alertTypeClass[alert.type]); | ||||
| 
 | ||||
|     if (alert.fade) { | ||||
|       classes.push("fade"); | ||||
|     } | ||||
| 
 | ||||
|     return classes.join(" "); | ||||
|   } | ||||
| 
 | ||||
|   if (!alerts.length) return null; | ||||
| 
 | ||||
|   return ( | ||||
|     <div> | ||||
|       {alerts.map((alert, index) => ( | ||||
|         <div key={index} className={cssClasses(alert)}> | ||||
|           <a className="close" onClick={() => removeAlert(alert)}> | ||||
|             × | ||||
|           </a> | ||||
|           <span>{alert.message}</span> | ||||
|         </div> | ||||
|       ))} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										37
									
								
								components/borderImage.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								components/borderImage.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import Image from "next/image"; | ||||
| import styles from "../styles/Components.module.css"; | ||||
| 
 | ||||
| export default function BorderImage(props) { | ||||
|   const { key, border, selected, size, onSelect, customImage } = props; | ||||
| 
 | ||||
|   const imageSize = size || 128; | ||||
| 
 | ||||
|   const classNames = [ | ||||
|     styles.borderKeeper, | ||||
|     selected == border.id ? styles.selected : undefined, | ||||
|   ].join(" "); | ||||
|   return ( | ||||
|     <div className={classNames} key={key}> | ||||
|       <Image | ||||
|         className={styles.userImage} | ||||
|         src={customImage || "/user.png"} | ||||
|         alt="user image" | ||||
|         width={imageSize} | ||||
|         height={imageSize} | ||||
|         onClick={(ev) => { | ||||
|           onSelect && onSelect(border.id); | ||||
|         }} | ||||
|       /> | ||||
|       <Image | ||||
|         className={styles.borderImage} | ||||
|         src={`/images/${border.imageName}`} | ||||
|         alt={`border with id ${border.id}`} | ||||
|         width={Math.floor(imageSize * 1.03125)} | ||||
|         height={Math.floor(imageSize * 1.03125)} | ||||
|         onClick={(ev) => { | ||||
|           onSelect && onSelect(border.id); | ||||
|         }} | ||||
|       /> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										46
									
								
								components/borderPreview.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								components/borderPreview.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| import BorderImage from "./borderImage"; | ||||
| import styles from "../styles/Components.module.css"; | ||||
| import { useSession } from "next-auth/react"; | ||||
| 
 | ||||
| export default function Preview(props) { | ||||
|   const { data: session } = useSession(); | ||||
|   const { data, current, selected, apply } = props; | ||||
| 
 | ||||
|   const currentItem = data?.filter( | ||||
|     (item) => parseInt(item.id) === (current != null ? parseInt(current) : 0) | ||||
|   )?.[0]; | ||||
| 
 | ||||
|   const selectedItem = data?.filter( | ||||
|     (item) => parseInt(item.id) === parseInt(selected) | ||||
|   )?.[0]; | ||||
| 
 | ||||
|   console.log(currentItem, selectedItem, session); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className={styles.preview}> | ||||
|       current | ||||
|       <br /> | ||||
|       {currentItem && ( | ||||
|         <BorderImage | ||||
|           border={currentItem} | ||||
|           selected={-1} | ||||
|           size={256} | ||||
|           customImage={session?.user?.image} | ||||
|         /> | ||||
|       )} | ||||
|       <br /> | ||||
|       new | ||||
|       <br /> | ||||
|       {selectedItem && ( | ||||
|         <BorderImage | ||||
|           border={selectedItem} | ||||
|           selected={-1} | ||||
|           size={256} | ||||
|           customImage={session?.user?.image} | ||||
|         /> | ||||
|       )} | ||||
|       <br /> | ||||
|       <button onClick={apply}>Apply</button> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|  | @ -1,3 +0,0 @@ | |||
| export default function BorderSelect() { | ||||
|      | ||||
| } | ||||
|  | @ -1,43 +1,47 @@ | |||
| import Image from "next/image"; | ||||
| import BorderImage from "./borderImage"; | ||||
| import InfiniteScroll from "react-infinite-scroll-component"; | ||||
| import { useEffect, useState } from "react"; | ||||
| import styles from "../styles/Components.module.css"; | ||||
| 
 | ||||
| const pageSize = 48; | ||||
| 
 | ||||
| export default function Select(props) { | ||||
|   const { data, onSelect, selected = {} } = props; | ||||
|   console.log("data", data, "selected", selected); | ||||
|   const { data, total, onSelect, selected = {} } = props; | ||||
|   console.log("data", data, "selected", selected, total); | ||||
| 
 | ||||
|   const [scrollIndex, setScrollIndex] = useState(36); | ||||
|   const [scrollData, setScrollData] = useState([]); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     setScrollData(data.slice(0, pageSize - 1)); | ||||
|   }, [data]); | ||||
| 
 | ||||
|   const fetchMore = () => { | ||||
|     let newData = data.slice(scrollIndex, scrollIndex + pageSize); | ||||
|     setScrollData([...scrollData, ...newData]); | ||||
|     setScrollIndex(scrollIndex + pageSize); | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <div className={styles.select}> | ||||
|       {data && | ||||
|         data.map((border, index) => { | ||||
|     <InfiniteScroll | ||||
|       dataLength={scrollData?.length || 0} | ||||
|       next={fetchMore} | ||||
|       hasMore={total > scrollData?.length || 0} | ||||
|       height={"75vh"} | ||||
|       className={styles.select} | ||||
|       loader={<h4>Loading...</h4>} | ||||
|       endMessage={<h4>No more borders.</h4>}> | ||||
|       {scrollData && | ||||
|         scrollData.map((border, index) => { | ||||
|           return ( | ||||
|             <div | ||||
|               className={[ | ||||
|                 styles.borderKeeper, | ||||
|                 selected == border.id ? styles.selected : undefined, | ||||
|               ].join(" ")} | ||||
|               key={border.id}> | ||||
|               <Image | ||||
|                 className={styles.userImage} | ||||
|                 src="/user.png" | ||||
|                 alt="user image" | ||||
|                 width={128} | ||||
|                 height={128} | ||||
|                 onClick={(ev) => { | ||||
|                   onSelect(border.id); | ||||
|                 }} | ||||
|               /> | ||||
|               <Image | ||||
|                 className={styles.borderImage} | ||||
|                 src={`/images/${border.imageName}`} | ||||
|                 alt={`border with id ${border.id}`} | ||||
|                 width={132} | ||||
|                 height={132} | ||||
|                 onClick={(ev) => { | ||||
|                   onSelect(border.id); | ||||
|                 }} | ||||
|               /> | ||||
|             </div> | ||||
|             <BorderImage | ||||
|               key={border.id} | ||||
|               border={border} | ||||
|               selected={selected} | ||||
|               onSelect={onSelect} | ||||
|             /> | ||||
|           ); | ||||
|         })} | ||||
|     </div> | ||||
|     </InfiniteScroll> | ||||
|   ); | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue