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";
|
import styles from "../styles/Components.module.css";
|
||||||
|
|
||||||
|
const pageSize = 48;
|
||||||
|
|
||||||
export default function Select(props) {
|
export default function Select(props) {
|
||||||
const { data, onSelect, selected = {} } = props;
|
const { data, total, onSelect, selected = {} } = props;
|
||||||
console.log("data", data, "selected", selected);
|
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 (
|
return (
|
||||||
<div className={styles.select}>
|
<InfiniteScroll
|
||||||
{data &&
|
dataLength={scrollData?.length || 0}
|
||||||
data.map((border, index) => {
|
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 (
|
return (
|
||||||
<div
|
<BorderImage
|
||||||
className={[
|
key={border.id}
|
||||||
styles.borderKeeper,
|
border={border}
|
||||||
selected == border.id ? styles.selected : undefined,
|
selected={selected}
|
||||||
].join(" ")}
|
onSelect={onSelect}
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</InfiniteScroll>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,21 @@ export const getBorderById = async (id) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const countAllBorders = async () => {
|
||||||
|
return await prisma.borderImage.count();
|
||||||
|
};
|
||||||
|
|
||||||
export const getAllBorders = async (limit = undefined, cursor = undefined) => {
|
export const getAllBorders = async (limit = undefined, cursor = undefined) => {
|
||||||
|
const sanitizedLimit = parseInt(limit) || undefined;
|
||||||
|
const sanitizedCursor = parseInt(cursor) || 0;
|
||||||
return await prisma.borderImage.findMany({
|
return await prisma.borderImage.findMany({
|
||||||
take: limit,
|
take: sanitizedLimit,
|
||||||
cursor: cursor,
|
cursor: {
|
||||||
|
id: sanitizedCursor,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
id: "asc",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -63,10 +74,10 @@ export const setUserBorder = async (req, borderId) => {
|
||||||
create: {
|
create: {
|
||||||
userId: session.user.id,
|
userId: session.user.id,
|
||||||
discordId: accountData.providerAccountId,
|
discordId: accountData.providerAccountId,
|
||||||
borderId,
|
borderId: parseInt(borderId),
|
||||||
},
|
},
|
||||||
data: {
|
update: {
|
||||||
borderId,
|
borderId: parseInt(borderId),
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
userId: session.user.id,
|
userId: session.user.id,
|
||||||
|
|
|
@ -8,4 +8,7 @@ module.exports = {
|
||||||
config.plugins.push(new webpack.EnvironmentPlugin(environment));
|
config.plugins.push(new webpack.EnvironmentPlugin(environment));
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
|
images: {
|
||||||
|
domains: ["localhost", "borders.j4.pm", "cdn.discordapp.com"],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,7 +19,9 @@
|
||||||
"node-fetch": "^3.2.3",
|
"node-fetch": "^3.2.3",
|
||||||
"prisma": "^3.12.0",
|
"prisma": "^3.12.0",
|
||||||
"react": "18.0.0",
|
"react": "18.0.0",
|
||||||
"react-dom": "18.0.0"
|
"react-dom": "18.0.0",
|
||||||
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
|
"rxjs": "^7.5.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "8.13.0",
|
"eslint": "8.13.0",
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import "../styles/globals.css";
|
import "../styles/globals.css";
|
||||||
import { SessionProvider } from "next-auth/react";
|
import { SessionProvider } from "next-auth/react";
|
||||||
|
import { Alert } from "../components/alert";
|
||||||
|
|
||||||
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
|
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
|
||||||
return (
|
return (
|
||||||
<SessionProvider session={session}>
|
<SessionProvider session={session}>
|
||||||
|
<Alert fade={true} />
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,6 +12,20 @@ export default NextAuth({
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
callbacks: {
|
callbacks: {
|
||||||
|
async signIn({ user, account, profile, email, credentials }) {
|
||||||
|
console.log(user, account, profile, email, credentials);
|
||||||
|
if (user.image != profile.image_url) {
|
||||||
|
await prisma.user.update({
|
||||||
|
data: {
|
||||||
|
image: profile.image_url,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
async session({ session, token, user }) {
|
async session({ session, token, user }) {
|
||||||
session.user.id = user.id;
|
session.user.id = user.id;
|
||||||
// console.log(JSON.stringify(user));
|
// console.log(JSON.stringify(user));
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { getAllBorders } from "../../../lib/borders";
|
import { getAllBorders, countAllBorders } from "../../../lib/borders";
|
||||||
|
|
||||||
export default function handler(req, res) {
|
export default function handler(req, res) {
|
||||||
getAllBorders().then((result) => {
|
getAllBorders(req.query?.limit, req.query?.cursor).then((result) => {
|
||||||
return res.status(200).json(result);
|
countAllBorders().then((count) => {
|
||||||
|
return res.status(200).json({ data: result, count });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
|
||||||
export default function handler(req, res) {
|
export default function handler(req, res) {
|
||||||
res.status(200).json({ name: 'John Doe' })
|
res.status(200).json({
|
||||||
|
ok: "ok",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
import { getUserBorders } from "../../../../lib/borders";
|
import { getUserBorders, setUserBorder } from "../../../../lib/borders";
|
||||||
|
|
||||||
export default function handler(req, res) {
|
export default function handler(req, res) {
|
||||||
|
if (req.method === "POST") {
|
||||||
|
setUserBorder(req, req.body).then((result) => {
|
||||||
|
if (result) {
|
||||||
|
return res.status(200).json(result);
|
||||||
|
} else {
|
||||||
|
return res.status(500).json({ error: "could not update border" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
getUserBorders(req).then((result) => {
|
getUserBorders(req).then((result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
return res.status(200).json(result);
|
return res.status(200).json(result);
|
||||||
|
@ -9,3 +18,4 @@ export default function handler(req, res) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,35 +1,91 @@
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import Router from "next/router";
|
||||||
import styles from "../styles/Home.module.css";
|
import styles from "../styles/Home.module.css";
|
||||||
import UserInfo from "../components/userInfo";
|
import UserInfo from "../components/userInfo";
|
||||||
|
import Preview from "../components/borderPreview";
|
||||||
import Select from "../components/select";
|
import Select from "../components/select";
|
||||||
|
import { alertService } from "../services";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [data, setData] = useState(null);
|
const [data, setData] = useState([]);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
const [borderData, setBorderData] = useState(null);
|
const [borderData, setBorderData] = useState(null);
|
||||||
const [selected, setSelected] = useState(0);
|
const [selected, setSelected] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
// const pageSize = 36;
|
||||||
fetch("api/border/all")
|
|
||||||
|
const [cursor, setCursor] = useState(0);
|
||||||
|
|
||||||
|
// const fetchMore = () => {
|
||||||
|
// console.log("fetch more");
|
||||||
|
// fetch(`api/border/all?limit=${pageSize}&cursor=${cursor}`)
|
||||||
|
// .then((res) => res.json())
|
||||||
|
// .then((res) => {
|
||||||
|
// setData([...data, ...res.data]);
|
||||||
|
// setTotal(res.count);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
const applyBorder = () => {
|
||||||
|
console.log("apply");
|
||||||
|
fetch("api/user/border/@me", { method: "POST", body: selected })
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => setData(data));
|
.then((res) => {
|
||||||
}, []);
|
console.log(res);
|
||||||
|
if (res.error) {
|
||||||
|
alertService.error(`error: ${res.error}`);
|
||||||
|
} else {
|
||||||
|
setBorderData(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("api/user/border/@me")
|
fetch("api/user/border/@me")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((dat) => {
|
||||||
|
console.log("border data", dat);
|
||||||
|
setBorderData(dat);
|
||||||
|
setSelected(dat?.borderId || 0);
|
||||||
|
});
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch(`api/border/all`)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setBorderData(data);
|
setData(data.data);
|
||||||
setSelected(data?.borderId || 0);
|
setTotal(data.count);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// let final = data?.[data?.length - 1];
|
||||||
|
// if (final) {
|
||||||
|
// setCursor(parseInt(final.id) + 1);
|
||||||
|
// console.log(cursor);
|
||||||
|
// }
|
||||||
|
// }, [data, cursor]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
// if data has not loaded in 5 seconds
|
||||||
|
if (!data || !borderData) {
|
||||||
|
console.log(data, borderData);
|
||||||
|
Router.reload();
|
||||||
|
} else {
|
||||||
|
console.log("data loaded properly, not reloading.");
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [data, borderData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Create Next App</title>
|
<title>Borders</title>
|
||||||
<meta name="description" content="Generated by create next app" />
|
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
<header>
|
<header>
|
||||||
|
@ -39,7 +95,18 @@ export default function Home() {
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<Select data={data} onSelect={setSelected} selected={selected} />
|
<Preview
|
||||||
|
data={data}
|
||||||
|
current={borderData?.borderId}
|
||||||
|
selected={selected}
|
||||||
|
apply={applyBorder}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
data={data}
|
||||||
|
total={total}
|
||||||
|
onSelect={setSelected}
|
||||||
|
selected={selected}
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,6 +12,8 @@ specifiers:
|
||||||
prisma: ^3.12.0
|
prisma: ^3.12.0
|
||||||
react: 18.0.0
|
react: 18.0.0
|
||||||
react-dom: 18.0.0
|
react-dom: 18.0.0
|
||||||
|
react-infinite-scroll-component: ^6.1.0
|
||||||
|
rxjs: ^7.5.5
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next-auth/prisma-adapter': 1.0.3_2b535bbfb604d219371c9582b52b11b1
|
'@next-auth/prisma-adapter': 1.0.3_2b535bbfb604d219371c9582b52b11b1
|
||||||
|
@ -23,6 +25,8 @@ dependencies:
|
||||||
prisma: 3.12.0
|
prisma: 3.12.0
|
||||||
react: 18.0.0
|
react: 18.0.0
|
||||||
react-dom: 18.0.0_react@18.0.0
|
react-dom: 18.0.0_react@18.0.0
|
||||||
|
react-infinite-scroll-component: 6.1.0_react@18.0.0
|
||||||
|
rxjs: 7.5.5
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
eslint: 8.13.0
|
eslint: 8.13.0
|
||||||
|
@ -1610,6 +1614,15 @@ packages:
|
||||||
scheduler: 0.21.0
|
scheduler: 0.21.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-infinite-scroll-component/6.1.0_react@18.0.0:
|
||||||
|
resolution: {integrity: sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.0.0'
|
||||||
|
dependencies:
|
||||||
|
react: 18.0.0
|
||||||
|
throttle-debounce: 2.3.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-is/16.13.1:
|
/react-is/16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1676,6 +1689,12 @@ packages:
|
||||||
queue-microtask: 1.2.3
|
queue-microtask: 1.2.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/rxjs/7.5.5:
|
||||||
|
resolution: {integrity: sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==}
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.4.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/scheduler/0.21.0:
|
/scheduler/0.21.0:
|
||||||
resolution: {integrity: sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==}
|
resolution: {integrity: sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1801,6 +1820,11 @@ packages:
|
||||||
resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=}
|
resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/throttle-debounce/2.3.0:
|
||||||
|
resolution: {integrity: sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/to-regex-range/5.0.1:
|
/to-regex-range/5.0.1:
|
||||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||||
engines: {node: '>=8.0'}
|
engines: {node: '>=8.0'}
|
||||||
|
@ -1821,6 +1845,10 @@ packages:
|
||||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/tslib/2.4.0:
|
||||||
|
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tsutils/3.21.0:
|
/tsutils/3.21.0:
|
||||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
|
@ -71,4 +71,5 @@ model BorderImage {
|
||||||
appId Int?
|
appId Int?
|
||||||
appName String?
|
appName String?
|
||||||
borderName String?
|
borderName String?
|
||||||
|
itemType Int?
|
||||||
}
|
}
|
||||||
|
|
56
services/alert.service.js
Normal file
56
services/alert.service.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { Subject } from "rxjs";
|
||||||
|
import { filter } from "rxjs/operators";
|
||||||
|
|
||||||
|
export const alertService = {
|
||||||
|
onAlert,
|
||||||
|
success,
|
||||||
|
error,
|
||||||
|
info,
|
||||||
|
warn,
|
||||||
|
alert,
|
||||||
|
clear,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AlertType = {
|
||||||
|
Success: "Success",
|
||||||
|
Error: "Error",
|
||||||
|
Info: "Info",
|
||||||
|
Warning: "Warning",
|
||||||
|
};
|
||||||
|
|
||||||
|
const alertSubject = new Subject();
|
||||||
|
const defaultId = "default-alert";
|
||||||
|
|
||||||
|
// enable subscribing to alerts observable
|
||||||
|
function onAlert(id = defaultId) {
|
||||||
|
return alertSubject.asObservable().pipe(filter((x) => x && x.id === id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// convenience methods
|
||||||
|
function success(message, options) {
|
||||||
|
alert({ ...options, type: AlertType.Success, message });
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(message, options) {
|
||||||
|
alert({ ...options, type: AlertType.Error, message });
|
||||||
|
}
|
||||||
|
|
||||||
|
function info(message, options) {
|
||||||
|
alert({ ...options, type: AlertType.Info, message });
|
||||||
|
}
|
||||||
|
|
||||||
|
function warn(message, options) {
|
||||||
|
alert({ ...options, type: AlertType.Warning, message });
|
||||||
|
}
|
||||||
|
|
||||||
|
// core alert method
|
||||||
|
function alert(alert) {
|
||||||
|
alert.id = alert.id || defaultId;
|
||||||
|
alert.autoClose = alert.autoClose === undefined ? true : alert.autoClose;
|
||||||
|
alertSubject.next(alert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear alerts
|
||||||
|
function clear(id = defaultId) {
|
||||||
|
alertSubject.next({ id });
|
||||||
|
}
|
1
services/index.js
Normal file
1
services/index.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from "./alert.service";
|
|
@ -2,27 +2,31 @@
|
||||||
min-height: 90vh;
|
min-height: 90vh;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
max-width: 50vw;
|
max-width: 50vw;
|
||||||
left: 45vw;
|
left: 40vw;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
max-width: 50vw;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.borderKeeper {
|
.borderKeeper {
|
||||||
width: 132px !important;
|
width: 132px !important;
|
||||||
height: 132px !important;
|
height: 132px !important;
|
||||||
padding: 4px;
|
padding: 6px;
|
||||||
|
margin: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.borderKeeper > span {
|
.borderKeeper > span {
|
||||||
display: inline-table !important;
|
display: inline-table !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.userImage,
|
.borderKeeper :nth-child(2) {
|
||||||
.borderImage {
|
top: -8em;
|
||||||
position: relative;
|
|
||||||
left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.userImage {
|
.userImage {
|
||||||
|
@ -32,9 +36,10 @@
|
||||||
|
|
||||||
.borderImage {
|
.borderImage {
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
top: -256px !important;
|
transform: scale(1.22);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
border: 1px solid black;
|
border: 3px solid black;
|
||||||
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: end;
|
justify-content: flex-end;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 50vw;
|
width: 50vw;
|
||||||
|
|
|
@ -14,3 +14,17 @@ a {
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
border: 1px black solid;
|
||||||
|
border-radius: 7%;
|
||||||
|
max-width: min-content;
|
||||||
|
padding: 4px;
|
||||||
|
background-color: lightgray;
|
||||||
|
position: relative;
|
||||||
|
left: 45vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
|
@ -70,6 +70,8 @@ async function main() {
|
||||||
const appid = border.appid;
|
const appid = border.appid;
|
||||||
const name = border.community_item_data.item_name;
|
const name = border.community_item_data.item_name;
|
||||||
|
|
||||||
|
const community_item_type = border.community_item_type;
|
||||||
|
|
||||||
const filename_large = border.community_item_data.item_image_large;
|
const filename_large = border.community_item_data.item_image_large;
|
||||||
if (filename_large == "c05c0155855b74c28e0f6e9417d4afa3c99d76ef.png") {
|
if (filename_large == "c05c0155855b74c28e0f6e9417d4afa3c99d76ef.png") {
|
||||||
console.log(border);
|
console.log(border);
|
||||||
|
@ -84,6 +86,7 @@ async function main() {
|
||||||
borders.push({
|
borders.push({
|
||||||
name: `${name}-LARGE`,
|
name: `${name}-LARGE`,
|
||||||
borderURL: `${borderURL}/${filename_large}`,
|
borderURL: `${borderURL}/${filename_large}`,
|
||||||
|
itemType: community_item_type,
|
||||||
appInfo,
|
appInfo,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -92,6 +95,7 @@ async function main() {
|
||||||
borders.push({
|
borders.push({
|
||||||
name: `${name}-SMALL`,
|
name: `${name}-SMALL`,
|
||||||
borderURL: `${borderURL}/${filename_small}`,
|
borderURL: `${borderURL}/${filename_small}`,
|
||||||
|
itemType: community_item_type,
|
||||||
appInfo,
|
appInfo,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ let catalogue = async () => {
|
||||||
id: 0,
|
id: 0,
|
||||||
imageName: item,
|
imageName: item,
|
||||||
borderName: "Default",
|
borderName: "Default",
|
||||||
|
itemType: 54,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
numAdded++;
|
numAdded++;
|
||||||
|
@ -95,12 +96,14 @@ let download = async () => {
|
||||||
appId: value.appInfo.appid,
|
appId: value.appInfo.appid,
|
||||||
appName: value.appInfo.name,
|
appName: value.appInfo.name,
|
||||||
borderName: value.name,
|
borderName: value.name,
|
||||||
|
itemType: value.itemType,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
imageName: filename,
|
imageName: filename,
|
||||||
appId: value.appInfo.appid,
|
appId: value.appInfo.appid,
|
||||||
appName: value.appInfo.name,
|
appName: value.appInfo.name,
|
||||||
borderName: value.name,
|
borderName: value.name,
|
||||||
|
itemType: value.itemType,
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
imageName: filename,
|
imageName: filename,
|
||||||
|
|
Loading…
Reference in a new issue