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