Optimize for Vercel, do some changes, entire site
This commit is contained in:
parent
d72cd85229
commit
f06256d97e
74 changed files with 5243 additions and 585 deletions
877
package-lock.json
generated
877
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -5,6 +5,7 @@
|
|||
"@types/lodash": "^4.14.195",
|
||||
"@types/node": "^20.3.1",
|
||||
"concurrently": "^8.2.0",
|
||||
"eslint": "^8.49.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"tsc-alias": "^1.8.6",
|
||||
"typescript": "^5.1.3"
|
||||
|
@ -34,6 +35,7 @@
|
|||
"crypto-js": "^4.1.1",
|
||||
"dotenv": "^16.3.0",
|
||||
"express": "^4.18.2",
|
||||
"formik": "^2.4.4",
|
||||
"lodash": "^4.17.21",
|
||||
"mongodb": "^5.6.0",
|
||||
"nanoid": "^3.3.6",
|
||||
|
|
|
@ -1,28 +1,31 @@
|
|||
import globals from "@/styles/global.module.css";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import styles from "./Ads.module.css";
|
||||
|
||||
export default function Ads() {
|
||||
const [hide, setHide] = useState(false);
|
||||
const [adService, setAdService] = useState(0);
|
||||
const int = useRef<NodeJS.Timer>();
|
||||
useEffect(() => {
|
||||
setHide(window.localStorage.getItem("hideAds") === "true");
|
||||
setAdService(Math.random());
|
||||
setInterval(() => setAdService(Math.random()), 60000);
|
||||
clearInterval(int.current);
|
||||
int.current = setInterval(() => setAdService(Math.random()), 60000);
|
||||
}, []);
|
||||
// Fix "partitioned storage access" on Firefox
|
||||
return !hide ? (
|
||||
<div className={globals.boxLike}>
|
||||
{adService < 0.2 ? (
|
||||
<iframe
|
||||
src="https://mothvertising.moth.monster/embed"
|
||||
className={styles.Mothvertisement}
|
||||
key={Math.random()}
|
||||
// key={Math.random()}
|
||||
></iframe>
|
||||
) : adService < 0.4 ? (
|
||||
<iframe
|
||||
src="https://dimden.neocities.org/navlink/"
|
||||
key={Math.random()}
|
||||
// key={Math.random()}
|
||||
className={styles.NavLink}
|
||||
width="180"
|
||||
height="180"
|
||||
|
@ -33,12 +36,12 @@ export default function Ads() {
|
|||
width="300"
|
||||
height="250"
|
||||
src="https://googol.neocities.org/neolink/embed.html"
|
||||
key={Math.random()}
|
||||
// key={Math.random()}
|
||||
></iframe>
|
||||
) : adService < 0.8 ? (
|
||||
<iframe
|
||||
src="https://john.citrons.xyz/embed?ref=example.com"
|
||||
key={Math.random()}
|
||||
// key={Math.random()}
|
||||
className={styles.Johnvertisement}
|
||||
width="732"
|
||||
height="90"
|
||||
|
@ -49,7 +52,7 @@ export default function Ads() {
|
|||
height="60"
|
||||
className={styles.NavLink}
|
||||
src="https://hbaguette.neocities.org/bannerlink/embed.html"
|
||||
key={Math.random()}
|
||||
// key={Math.random()}
|
||||
></iframe>
|
||||
)}
|
||||
<div className={globals.horizontalListLeft}>
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
.Box.ltr {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.Box .innerContent {
|
||||
padding: 6px 4px;
|
||||
display: inline-flex;
|
||||
|
|
|
@ -29,7 +29,11 @@ export default function Box({
|
|||
) as React.CSSProperties;
|
||||
return (
|
||||
<div
|
||||
className={styles.Box + (!properties?.nfw ? " " + styles.fw : "")}
|
||||
className={
|
||||
styles.Box +
|
||||
(!properties?.nfw ? " " + styles.fw : "") +
|
||||
(properties?.ltr ? " " + styles.ltr : "")
|
||||
}
|
||||
style={style}
|
||||
>
|
||||
<Conditional condition={properties?.title != null}>
|
||||
|
|
|
@ -8,5 +8,6 @@ export type BoxConfig = {
|
|||
};
|
||||
theme?: Color3;
|
||||
nfw?: boolean;
|
||||
ltr?: boolean;
|
||||
class?: string;
|
||||
};
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
text-align: center;
|
||||
width: 100%;
|
||||
gap: 16px;
|
||||
flex: 2 1;
|
||||
}
|
||||
|
||||
.Nav .right {
|
||||
|
@ -37,6 +38,7 @@
|
|||
text-align: center;
|
||||
width: 100%;
|
||||
gap: 16px;
|
||||
flex: 0.5 1;
|
||||
}
|
||||
|
||||
.Nav .titlePath {
|
||||
|
|
|
@ -23,6 +23,7 @@ export default function Nav(elementProps: AnyObject) {
|
|||
href="/"
|
||||
>
|
||||
TrollCall
|
||||
<span className={globals.small}> r4 Beta</span>
|
||||
</Link>
|
||||
</span>
|
||||
<span className={globals.title + " " + styles.shortTitle}>
|
||||
|
@ -30,10 +31,18 @@ export default function Nav(elementProps: AnyObject) {
|
|||
className={globals.link}
|
||||
href="/"
|
||||
>
|
||||
TC
|
||||
TC<span className={globals.small}> Beta</span>
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
<span className={globals.icon}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://github.com/MeowcaTheoRange/TrollCallAPIs/issues"
|
||||
>
|
||||
bug_report
|
||||
</Link>
|
||||
</span>
|
||||
<span className={globals.icon}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
|
@ -82,7 +91,7 @@ export default function Nav(elementProps: AnyObject) {
|
|||
</span>
|
||||
<span className={globals.icon}>
|
||||
<Link
|
||||
href={`/logout`}
|
||||
href={`/manage`}
|
||||
className={globals.link}
|
||||
>
|
||||
logout
|
||||
|
@ -90,7 +99,24 @@ export default function Nav(elementProps: AnyObject) {
|
|||
</span>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
<>
|
||||
<span className={globals.icon}>
|
||||
<Link
|
||||
href={`/add/clan`}
|
||||
className={globals.link}
|
||||
>
|
||||
group_add
|
||||
</Link>
|
||||
</span>
|
||||
<span className={globals.icon}>
|
||||
<Link
|
||||
href={`/manage`}
|
||||
className={globals.link}
|
||||
>
|
||||
login
|
||||
</Link>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -28,9 +28,9 @@
|
|||
.ClanCard .horizontal {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
justify-content: center;
|
||||
justify-content: start;
|
||||
gap: 16px;
|
||||
padding: 8px;
|
||||
padding: 8px 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -58,12 +58,12 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
/* overflow: hidden; */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ClanCard .horizontal .horizontalRight > * {
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
/* overflow: hidden; */
|
||||
}
|
||||
|
|
|
@ -61,11 +61,13 @@ export default function ClanCard({
|
|||
{clan.displayName ?? clan.name}
|
||||
</ConditionalParent>
|
||||
</p>
|
||||
<Conditional condition={clan.flairs != null}>
|
||||
<div className={globals.horizontalListLeft}>
|
||||
{clan.flairs.map(flair => (
|
||||
{clan.flairs?.map(flair => (
|
||||
<FlairCard flair={flair} />
|
||||
))}
|
||||
</div>
|
||||
</Conditional>
|
||||
<hr className={globals.invisep} />
|
||||
<p className={globals.iconText}>
|
||||
<span className={globals.iconSmall}>group</span>
|
||||
|
@ -93,10 +95,16 @@ export default function ClanCard({
|
|||
</span>
|
||||
</p>
|
||||
</Conditional>
|
||||
<Conditional condition={clan.description.length > 0}>
|
||||
<p className={globals.iconText}>
|
||||
<span className={globals.iconSmall}>description</span>
|
||||
<span className={globals.text}>{clan.description}</span>
|
||||
<span className={globals.iconSmall}>
|
||||
description
|
||||
</span>
|
||||
<span className={globals.text}>
|
||||
{clan.description}
|
||||
</span>
|
||||
</p>
|
||||
</Conditional>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
|
|
|
@ -14,29 +14,22 @@ export default function ClanSkeleton() {
|
|||
<div className={styles.horizontal}>
|
||||
<div className={styles.horizontalLeft}></div>
|
||||
<div className={styles.horizontalRight}>
|
||||
<p className={globals.title}>
|
||||
fjfjfjf fjf fjfjfjfjfjfj fj fj fjfjfj
|
||||
</p>
|
||||
<p className={globals.title}>Epic Megagames</p>
|
||||
<p className={globals.iconText}>
|
||||
<span className={globals.iconSmall}>group</span>
|
||||
<span className={globals.text}>
|
||||
jjffjf j f fjfjfjfjjjjff jffff
|
||||
Tim Sweeney (88/12)
|
||||
</span>
|
||||
</p>
|
||||
<p className={globals.iconText}>
|
||||
<span className={globals.iconSmall}>link</span>
|
||||
<span className={globals.text}>
|
||||
https://fjfjfjfjfjfjfjfjfjfjf.fjf/fjfjfjfj
|
||||
https://trollcall.xyz/
|
||||
</span>
|
||||
</p>
|
||||
<p className={globals.iconText}>
|
||||
<span className={globals.iconSmall}>description</span>
|
||||
<span className={globals.text}>
|
||||
ffffffffffffffffffjjjfjf fj j jjffjf
|
||||
jfjfjfjfjfjjfffjffjjjjfjfjffjfjj ffjfjf fj j fj
|
||||
fjfjjfjfj jjffjjjfjfjfjfjfjjfj fj fj jf jf jf jf
|
||||
fjjfjffjfjfj fjffjf fjfj
|
||||
</span>
|
||||
<span className={globals.text}>Tim Sweeney</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
.FlairLink {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.FlairCard {
|
||||
background-color: var(--pri-bg);
|
||||
color: var(--pri-fg);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
/* eslint-disable @next/next/no-img-element */
|
||||
import { Color3 } from "@/types/assist/color";
|
||||
import { ClientFlair } from "@/types/flair";
|
||||
import { ConditionalParent } from "@/utility/react/Conditional";
|
||||
import { ThemeModeContext } from "@/utility/react/Themer";
|
||||
import Link from "next/link";
|
||||
import { useContext } from "react";
|
||||
import styles from "./FlairCard.module.css";
|
||||
|
||||
|
@ -20,6 +22,18 @@ export default function FlairCard({ flair }: { flair: ClientFlair }) {
|
|||
}
|
||||
) as React.CSSProperties;
|
||||
return (
|
||||
<ConditionalParent
|
||||
condition={flair.link != null}
|
||||
parent={children => (
|
||||
<Link
|
||||
className={styles.FlairLink}
|
||||
href={flair.link ?? ""}
|
||||
target="_blank"
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={styles.FlairCard}
|
||||
style={style}
|
||||
|
@ -27,5 +41,6 @@ export default function FlairCard({ flair }: { flair: ClientFlair }) {
|
|||
>
|
||||
{flair.name}
|
||||
</div>
|
||||
</ConditionalParent>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,3 +31,14 @@
|
|||
drop-shadow(1px -1px 0 currentcolor)
|
||||
drop-shadow(-1px 1px 0 currentcolor);
|
||||
}
|
||||
.SignCard.Skeleton .topImagePlaceholder {
|
||||
font-family: "Renogare", "Poppins", "Space Grotesk", "Fira Code",
|
||||
"Courier New", monospace;
|
||||
font-size: 52px;
|
||||
width: 52px;
|
||||
color: var(--pri-bg);
|
||||
filter: drop-shadow(1px 1px 0 var(--pri-fg))
|
||||
drop-shadow(-1px -1px 0 var(--pri-fg))
|
||||
drop-shadow(1px -1px 0 var(--pri-fg))
|
||||
drop-shadow(-1px 1px 0 var(--pri-fg));
|
||||
}
|
||||
|
|
|
@ -12,18 +12,12 @@ export default function SignSkeleton() {
|
|||
}}
|
||||
>
|
||||
<div className={styles.gridItem}>
|
||||
<img
|
||||
src={"/assets/signs/Lime/Cancer.svg"}
|
||||
className={styles.topImage}
|
||||
alt=""
|
||||
></img>
|
||||
<span className={styles.topImagePlaceholder}>λ</span>
|
||||
</div>
|
||||
<div className={styles.gridItem + " " + styles.secondary}>
|
||||
<p className={globals.title}>jjjjfjfjffjfjjffj</p>
|
||||
<p className={globals.title}>half-life</p>
|
||||
<p className={globals.horizontalList}>
|
||||
<span className={globals.text}>jjjfjfj</span>
|
||||
<span className={globals.text}>+</span>
|
||||
<span className={globals.text}>jjjfjfj</span>
|
||||
<span className={globals.text}>born.</span>
|
||||
</p>
|
||||
</div>
|
||||
</Box>
|
||||
|
|
|
@ -82,15 +82,26 @@ export default function TrollCard({
|
|||
<p className={globals.text}>
|
||||
({troll.pronunciation.join(" ")})
|
||||
</p>
|
||||
<Conditional condition={troll.username != null}>
|
||||
<p className={globals.text}>
|
||||
Also known as <b>{troll.username}</b> online.
|
||||
</p>
|
||||
</Conditional>
|
||||
<Conditional condition={troll.flairs != null}>
|
||||
<div className={globals.horizontalListLeft}>
|
||||
{troll.flairs.map(flair => (
|
||||
{troll.flairs?.map(flair => (
|
||||
<FlairCard flair={flair} />
|
||||
))}
|
||||
</div>
|
||||
</Conditional>
|
||||
<hr className={globals.invisep} />
|
||||
<Conditional
|
||||
condition={
|
||||
troll.class != null &&
|
||||
troll.gender != null &&
|
||||
troll.species != null
|
||||
}
|
||||
>
|
||||
<p className={globals.horizontalList}>
|
||||
<Conditional condition={troll.class != null}>
|
||||
<span className={globals.text}>
|
||||
|
@ -99,29 +110,41 @@ export default function TrollCard({
|
|||
</span>
|
||||
<span className={globals.text}>-</span>
|
||||
</Conditional>
|
||||
<span className={globals.text}>{troll.gender}</span>
|
||||
<span className={globals.text}>-</span>
|
||||
<Conditional condition={troll.gender != null}>
|
||||
<span className={globals.text}>
|
||||
{PronounGrouper(troll.pronouns)}
|
||||
{troll.gender}
|
||||
</span>
|
||||
</Conditional>
|
||||
<Conditional
|
||||
condition={
|
||||
troll.gender != null &&
|
||||
troll.species != null
|
||||
}
|
||||
>
|
||||
<span className={globals.text}>-</span>
|
||||
</Conditional>
|
||||
<Conditional condition={troll.species != null}>
|
||||
<span className={globals.text}>
|
||||
{troll.species}
|
||||
</span>
|
||||
</Conditional>
|
||||
</p>
|
||||
</Conditional>
|
||||
<p className={globals.horizontalList}>
|
||||
<span className={globals.text}>
|
||||
{AgeConverter(troll.age, true)}
|
||||
{AgeConverter(troll.age)} old
|
||||
</span>
|
||||
<span className={globals.text}>-</span>
|
||||
<span className={globals.text}>
|
||||
{HeightConverter(troll.height)}
|
||||
</span>
|
||||
<Conditional condition={troll.species != null}>
|
||||
<span className={globals.text}>-</span>
|
||||
<span className={globals.text}>
|
||||
{troll.species}
|
||||
{PronounGrouper(troll.pronouns)}
|
||||
</span>
|
||||
</Conditional>
|
||||
</p>{" "}
|
||||
<hr className={globals.invisep} />
|
||||
<Conditional condition={troll.facts != null}>
|
||||
<hr className={globals.invisep} />
|
||||
<ul>
|
||||
{troll.facts?.map((fact, index) => (
|
||||
<li
|
||||
|
@ -133,8 +156,8 @@ export default function TrollCard({
|
|||
))}
|
||||
</ul>
|
||||
</Conditional>{" "}
|
||||
<hr className={globals.invisep} />
|
||||
<Conditional condition={troll.trueSign != null}>
|
||||
<hr className={globals.invisep} />
|
||||
<div
|
||||
className={
|
||||
styles.stack +
|
||||
|
@ -157,7 +180,14 @@ export default function TrollCard({
|
|||
</Conditional>
|
||||
</div>
|
||||
</div>
|
||||
<Conditional condition={small}>
|
||||
<Conditional
|
||||
condition={
|
||||
small &&
|
||||
randomLove != "" &&
|
||||
randomHate != "" &&
|
||||
randomQuote != ""
|
||||
}
|
||||
>
|
||||
<div className={styles.bottom}>
|
||||
<Conditional condition={randomLove != ""}>
|
||||
<p className={globals.iconText}>
|
||||
|
@ -179,10 +209,12 @@ export default function TrollCard({
|
|||
format_quote
|
||||
</span>
|
||||
<span className={globals.text}>
|
||||
{parseQuirk(
|
||||
{troll.quirks != null
|
||||
? parseQuirk(
|
||||
randomQuote,
|
||||
troll.quirks["default"]
|
||||
)}
|
||||
)
|
||||
: randomQuote}
|
||||
</span>
|
||||
</p>
|
||||
</Conditional>
|
||||
|
|
|
@ -19,33 +19,30 @@ export default function TrollSkeleton() {
|
|||
></img>
|
||||
</div>
|
||||
<div className={styles.gridItem + " " + styles.secondary}>
|
||||
<p className={globals.title}>ffjfjf fjfjjj</p>
|
||||
<p className={globals.text}>(fjjjf-jjfj fjjfjf-fjffj)</p>
|
||||
<p className={globals.title}>GABBEN NEWELL</p>
|
||||
<p className={globals.text}>(gay-ben new-wul)</p>
|
||||
<p className={globals.text}>
|
||||
Also known as ffjfjjfjjfjfjjjjjfjjf online.
|
||||
Also known as gaben@valvesoftware.com online.
|
||||
</p>
|
||||
<hr className={globals.invisep} />
|
||||
<p className={globals.horizontalList}>
|
||||
<span className={globals.text}>fj</span>
|
||||
<span className={globals.text}>Lord of Sales</span>
|
||||
<span className={globals.text}>-</span>
|
||||
<span className={globals.text}>ffjfj</span>
|
||||
<span className={globals.text}>Software</span>
|
||||
<span className={globals.text}>-</span>
|
||||
<span className={globals.text}>fj/fjj/fjjjf</span>
|
||||
<span className={globals.text}>de/ck</span>
|
||||
</p>
|
||||
<p className={globals.horizontalList}>
|
||||
<span className={globals.text}>fj</span>
|
||||
<span className={globals.text}>All Time</span>
|
||||
<span className={globals.text}>-</span>
|
||||
<span className={globals.text}>ffjfj</span>
|
||||
<span className={globals.text}>30%</span>
|
||||
<span className={globals.text}>-</span>
|
||||
<span className={globals.text}>fj/fjj/fjjjf</span>
|
||||
<span className={globals.text}>Steam</span>
|
||||
</p>
|
||||
<hr className={globals.invisep} />
|
||||
<ul>
|
||||
<li className={globals.text}>fjjfjjfjffj</li>
|
||||
<li className={globals.text}>fjjjjfjffjjfffjfj</li>
|
||||
<li className={globals.text}>
|
||||
fjjjjffjjjfjjjfjjffjfjfj
|
||||
</li>
|
||||
<li className={globals.text}>Steam Summer Sale</li>
|
||||
<li className={globals.text}>Dota</li>
|
||||
</ul>
|
||||
<br />
|
||||
<div className={styles.stack + " " + styles.noFalseSign}>
|
||||
|
@ -56,20 +53,16 @@ export default function TrollSkeleton() {
|
|||
<div className={styles.bottom}>
|
||||
<p className={globals.iconText}>
|
||||
<span className={globals.iconSmall}>thumb_up</span>
|
||||
<span className={globals.text}>
|
||||
ffjj fjf jjffffjfjfj fjfjjfjfjjj
|
||||
</span>
|
||||
<span className={globals.text}>n < 3</span>
|
||||
</p>
|
||||
<p className={globals.iconText}>
|
||||
<span className={globals.iconSmall}>thumb_down</span>
|
||||
<span className={globals.text}>
|
||||
ffjjj fjjfjjf jf jj jjfjjjffj j fff
|
||||
</span>
|
||||
<span className={globals.text}>n >= 3</span>
|
||||
</p>
|
||||
<p className={globals.iconText}>
|
||||
<span className={globals.iconSmall}>format_quote</span>
|
||||
<span className={globals.text}>
|
||||
jjj fjjf jfjjjf f j ffff j f jjfj jf fj fjjjfj jfj
|
||||
Hopefully, it would have been worth the wait.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
49
src/components/form/ErrorHandler/ErrorHandler.tsx
Normal file
49
src/components/form/ErrorHandler/ErrorHandler.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
import globals from "@/styles/global.module.css";
|
||||
import form_globals from "@/styles/global_form.module.css";
|
||||
export default function ErrorHandler(error: any) {
|
||||
console.log(error);
|
||||
if (Array.isArray(error) && error.map)
|
||||
return (
|
||||
<span className={form_globals.verticalListCrunch}>
|
||||
{error.map((errorChild, index) => (
|
||||
<ErrorHandler
|
||||
error={errorChild}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
else if (typeof error === "string")
|
||||
return (
|
||||
<span className={`${globals.text} ${form_globals.render_error}`}>
|
||||
<span className={form_globals.iconSmall}>error</span> Error:{" "}
|
||||
{error.replace(
|
||||
/\["?(\d+)"?\]/g,
|
||||
(_: string, s1: string, __: any, ___: string) =>
|
||||
" field #" + (+s1 + 1)
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
else if (Array.isArray(error.error) && error.error.map)
|
||||
return (
|
||||
<span className={form_globals.verticalListCrunch}>
|
||||
{error.error.map((errorChild: string, index: number) => (
|
||||
<ErrorHandler
|
||||
error={errorChild}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
else if (typeof error.error === "string")
|
||||
return (
|
||||
<span className={`${globals.text} ${form_globals.render_error}`}>
|
||||
<span className={form_globals.iconSmall}>error</span> Error:{" "}
|
||||
{error.error.replace(
|
||||
/\["?(\d+)"?\]/g,
|
||||
(_: string, s1: string, __: any, ___: string) =>
|
||||
" field #" + (+s1 + 1)
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
18
src/components/form/template/.prettierrc
Normal file
18
src/components/form/template/.prettierrc
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"_comment": "A custom .prettierrc file is here because Formik gets pretty intense.",
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"printWidth": 160,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"quoteProps": "preserve",
|
||||
"jsxSingleQuote": false,
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "avoid",
|
||||
"proseWrap": "never",
|
||||
"endOfLine": "lf",
|
||||
"embeddedLanguageFormatting": "auto",
|
||||
"singleAttributePerLine": true
|
||||
}
|
730
src/components/form/template/clan.tsx
Normal file
730
src/components/form/template/clan.tsx
Normal file
|
@ -0,0 +1,730 @@
|
|||
import Box from "@/components/Box/Box";
|
||||
import ErrorHandler from "@/components/form/ErrorHandler/ErrorHandler";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import form_globals from "@/styles/global_form.module.css";
|
||||
import { Color3 } from "@/types/assist/color";
|
||||
import { SubmitClan, SubmitClanSchema } from "@/types/client/clan";
|
||||
import { ArrayHelpers, ErrorMessage, Field, FieldArray, Form, Formik } from "formik";
|
||||
import { NextRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function ClanFormTemplate({
|
||||
router,
|
||||
onSubmitURI,
|
||||
on200URI,
|
||||
initialValues,
|
||||
method
|
||||
}: {
|
||||
router: NextRouter;
|
||||
initialValues?: SubmitClan;
|
||||
onSubmitURI?: string;
|
||||
on200URI?: string;
|
||||
method?: string;
|
||||
}) {
|
||||
const [submitError, setSubmitError] = useState("");
|
||||
return (
|
||||
<Formik
|
||||
initialValues={
|
||||
initialValues ??
|
||||
({
|
||||
name: "",
|
||||
description: "",
|
||||
members: [
|
||||
{
|
||||
name: "",
|
||||
pronouns: [["", "", ""]]
|
||||
}
|
||||
]
|
||||
} as SubmitClan)
|
||||
}
|
||||
validationSchema={SubmitClanSchema}
|
||||
onSubmit={async (values, { setSubmitting, setErrors, setFieldError }) => {
|
||||
fetch(onSubmitURI ?? "/api/clan", {
|
||||
method: method ?? "POST",
|
||||
body: JSON.stringify(values),
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.status === 200) router.push(on200URI ?? `/clan/${values.name}`);
|
||||
else setSubmitError(res.status.toString());
|
||||
});
|
||||
}}
|
||||
>
|
||||
{({ values, setFieldValue, errors, initialValues, isSubmitting, resetForm, submitForm }) => (
|
||||
<Form className={form_globals.FlexNormalizer}>
|
||||
<Box properties={{ title: { text: "Identity" } }}>
|
||||
<span className={globals.text}>About your clan. Name, members, whatever else!</span>
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={form_globals.verticalListCrunch}>
|
||||
<p className={globals.titleSmall}>Name</p>
|
||||
<span className={globals.text}>The name of your clan.</span>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={globals.horizontalListLeft}>
|
||||
<Field
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder="jim"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
name="name"
|
||||
render={ErrorHandler}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
${form_globals.verticalListCrunch}
|
||||
${form_globals.layoutAppearable}
|
||||
${values.name != initialValues.name && values.name != null && errors.name == null ? form_globals.appear : ""}
|
||||
`}
|
||||
>
|
||||
<span className={globals.text}>By the way, your clan's link is based on the this name.</span>
|
||||
<span className={globals.mono}>/clan/{values.name?.toLowerCase()}</span>
|
||||
<span className={`${form_globals.render_info}`}>
|
||||
<span className={form_globals.iconSmall}>info</span>{" "}
|
||||
<span className={`${globals.text}`}>
|
||||
Keep in mind - if you have overlapping names with another clan, you will not be able to submit this clan.
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr className={globals.invisep} />
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={form_globals.verticalListCrunch}>
|
||||
<p className={globals.titleSmall}>Display name</p>
|
||||
<span className={globals.text}>The display name for your clan. If left blank, the simple name will be used instead.</span>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={globals.horizontalListLeft}>
|
||||
<Field
|
||||
type="text"
|
||||
name="displayName"
|
||||
placeholder="Clan of Jim"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
name="displayName"
|
||||
render={ErrorHandler}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
${form_globals.verticalListCrunch}
|
||||
${form_globals.layoutAppearable}
|
||||
${values.displayName != initialValues.displayName && errors.displayName == null ? form_globals.appear : ""}
|
||||
`}
|
||||
>
|
||||
<span className={`${form_globals.render_info}`}>
|
||||
<span className={form_globals.iconSmall}>info</span>{" "}
|
||||
<span className={`${globals.text}`}>
|
||||
If you have overlapping display names with another clan, nothing will happen -- though you may have issues regarding
|
||||
impersonation.
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr className={globals.invisep} />
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={form_globals.verticalListCrunch}>
|
||||
<p className={globals.titleSmall}>Members</p>
|
||||
<span className={globals.text}>All of the members of your clan.</span>
|
||||
</div>
|
||||
<FieldArray
|
||||
name="members"
|
||||
render={(arrayHelpers: ArrayHelpers) => (
|
||||
<div className={globals.verticalListTop}>
|
||||
{values.members && values.members.length > 0 ? (
|
||||
<>
|
||||
<div className={form_globals.horizlist}>
|
||||
<button
|
||||
className={`${globals.button}`}
|
||||
type="button"
|
||||
onClick={() => arrayHelpers.unshift({ name: "", pronouns: [["", "", ""]] })}
|
||||
>
|
||||
Add member
|
||||
</button>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
{values.members.map((member, index) => (
|
||||
<div
|
||||
className={globals.verticalListTop}
|
||||
key={index}
|
||||
>
|
||||
<div className={form_globals.horizlist}>
|
||||
<Field
|
||||
type="text"
|
||||
name={`members.${index}.name`}
|
||||
placeholder="Jimberly"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
name={`members.${index}.name`}
|
||||
render={ErrorHandler}
|
||||
/>
|
||||
<div className={form_globals.horizlist}>
|
||||
<FieldArray
|
||||
name={`members.${index}.pronouns`}
|
||||
render={(arrayPronounHelpers: ArrayHelpers) => (
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={form_globals.horizlist}>
|
||||
<button
|
||||
className={`${globals.button}`}
|
||||
type="button"
|
||||
onClick={() => arrayPronounHelpers.unshift(["", "", ""])}
|
||||
>
|
||||
Add pronoun set
|
||||
</button>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
{values.members[index].pronouns && values.members[index].pronouns.length > 0 ? (
|
||||
values.members[index].pronouns.map((pronounSet, mbPindex) => (
|
||||
<div
|
||||
className={form_globals.horizlist}
|
||||
key={index}
|
||||
>
|
||||
<div className={form_globals.horizlist}>
|
||||
<Field
|
||||
type="text"
|
||||
name={`members.${index}.pronouns.${mbPindex}.0`}
|
||||
placeholder="they"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputSmall}
|
||||
${form_globals.textLikeInputInvisible}
|
||||
`}
|
||||
/>
|
||||
<span className={globals.text}>/</span>
|
||||
<Field
|
||||
type="text"
|
||||
name={`members.${index}.pronouns.${mbPindex}.1`}
|
||||
placeholder="them"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputSmall}
|
||||
${form_globals.textLikeInputInvisible}
|
||||
`}
|
||||
/>
|
||||
<span className={globals.text}>/</span>
|
||||
<Field
|
||||
type="text"
|
||||
name={`members.${index}.pronouns.${mbPindex}.2`}
|
||||
placeholder="theirs"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputSmall}
|
||||
${form_globals.textLikeInputInvisible}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<div className={form_globals.horizlist}>
|
||||
<button
|
||||
className={`${globals.buttonIcon}`}
|
||||
type="button"
|
||||
onClick={() => arrayPronounHelpers.remove(mbPindex)}
|
||||
>
|
||||
remove
|
||||
</button>
|
||||
<button
|
||||
className={`${globals.buttonIcon}`}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
arrayPronounHelpers.insert(mbPindex + 1, ["", "", ""])
|
||||
}
|
||||
>
|
||||
add
|
||||
</button>
|
||||
<button
|
||||
className={`${globals.buttonIcon} ${
|
||||
mbPindex <= 0 ? form_globals.layoutButtonDisabled : ""
|
||||
}`}
|
||||
disabled={mbPindex <= 0}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
arrayPronounHelpers.move(mbPindex, mbPindex - 1)
|
||||
}
|
||||
>
|
||||
keyboard_arrow_up
|
||||
</button>
|
||||
<button
|
||||
className={`${globals.buttonIcon} ${
|
||||
mbPindex >= values.members[index].pronouns.length - 1
|
||||
? form_globals.layoutButtonDisabled
|
||||
: ""
|
||||
}`}
|
||||
disabled={
|
||||
mbPindex >= values.members[index].pronouns.length - 1
|
||||
}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
arrayPronounHelpers.move(mbPindex, mbPindex + 1)
|
||||
}
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
name={`members.${index}.pronouns`}
|
||||
render={ErrorHandler}
|
||||
/>
|
||||
<div className={form_globals.horizlist}>
|
||||
<button
|
||||
className={`${globals.buttonIcon}`}
|
||||
type="button"
|
||||
onClick={() => arrayHelpers.remove(index)}
|
||||
>
|
||||
remove
|
||||
</button>
|
||||
<button
|
||||
className={`${globals.buttonIcon}`}
|
||||
type="button"
|
||||
onClick={() => arrayHelpers.insert(index + 1, { name: "", pronouns: [["", "", ""]] })}
|
||||
>
|
||||
add
|
||||
</button>
|
||||
<button
|
||||
className={`${globals.buttonIcon} ${index <= 0 ? form_globals.layoutButtonDisabled : ""}`}
|
||||
disabled={index <= 0}
|
||||
type="button"
|
||||
onClick={() => arrayHelpers.move(index, index - 1)}
|
||||
>
|
||||
keyboard_arrow_up
|
||||
</button>
|
||||
<button
|
||||
className={`${globals.buttonIcon} ${
|
||||
index >= values.members.length - 1 ? form_globals.layoutButtonDisabled : ""
|
||||
}`}
|
||||
disabled={index >= values.members.length - 1}
|
||||
type="button"
|
||||
onClick={() => arrayHelpers.move(index, index + 1)}
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
className={`${globals.button}`}
|
||||
type="button"
|
||||
onClick={() => arrayHelpers.unshift({ name: "", pronouns: [["", "", ""]] })}
|
||||
>
|
||||
Add members
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<ErrorMessage
|
||||
name={`members`}
|
||||
render={ErrorHandler}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<hr className={globals.invisep} />
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={form_globals.verticalListCrunch}>
|
||||
<p className={globals.titleSmall}>description</p>
|
||||
<span className={globals.text}>Describe your clan to us!</span>
|
||||
<span className={globals.text}>You don't have to, but it would be cool if you did.</span>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
<Field
|
||||
as="textarea"
|
||||
name="description"
|
||||
placeholder="Description"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputMultiline}
|
||||
`}
|
||||
/>
|
||||
<span
|
||||
className={`${globals.text} ${values.description.length > 10000 ? form_globals.render_error : ""} ${
|
||||
values.description.length >= 9900 ? form_globals.render_warning : ""
|
||||
}`}
|
||||
>
|
||||
{values.description.length <= 10000
|
||||
? 10000 - values.description.length + " characters remaining"
|
||||
: values.description.length - 10000 + " characters over limit"}
|
||||
</span>
|
||||
<ErrorMessage
|
||||
name="description"
|
||||
render={ErrorHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr className={globals.invisep} />
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={form_globals.verticalListCrunch}>
|
||||
<p className={globals.titleSmall}>Clan Color</p>
|
||||
<span className={globals.text}>The color that represents your clan the most.</span>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
{values.color != null && errors.color == null ? (
|
||||
<>
|
||||
<div className={globals.horizontalListLeft}>
|
||||
<span className={globals.text}>Red</span>
|
||||
<Field
|
||||
type="number"
|
||||
name="color.0"
|
||||
placeholder="0"
|
||||
min="0"
|
||||
max="255"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputSmall}
|
||||
${form_globals.textLikeInputInvisible}
|
||||
`}
|
||||
/>
|
||||
<span className={globals.text}>Green</span>
|
||||
<Field
|
||||
type="number"
|
||||
name="color.1"
|
||||
placeholder="0"
|
||||
min="0"
|
||||
max="255"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputSmall}
|
||||
${form_globals.textLikeInputInvisible}
|
||||
`}
|
||||
/>
|
||||
<span className={globals.text}>Blue</span>
|
||||
<Field
|
||||
type="number"
|
||||
name="color.2"
|
||||
placeholder="0"
|
||||
min="0"
|
||||
max="255"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputSmall}
|
||||
${form_globals.textLikeInputInvisible}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<span className={globals.text}>#{Color3.fromRGB(...values.color).toHex()}</span>
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text: "This is a title."
|
||||
},
|
||||
theme: Color3.fromRGB(...values.color)
|
||||
}}
|
||||
>
|
||||
<span className={globals.text}>This is average text!</span>
|
||||
<button
|
||||
className={globals.button}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setFieldValue("color", undefined);
|
||||
}}
|
||||
>
|
||||
Clear Clan Color
|
||||
</button>
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
className={globals.button}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setFieldValue("color", [255, 255, 255]);
|
||||
}}
|
||||
>
|
||||
Add Clan Color
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ErrorMessage
|
||||
name="color"
|
||||
render={ErrorHandler}
|
||||
/>
|
||||
</div>
|
||||
<hr className={globals.invisep} />
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={form_globals.verticalListCrunch}>
|
||||
<p className={globals.titleSmall}>PFP Image</p>
|
||||
<span className={globals.text}>An image.</span>
|
||||
</div>
|
||||
<div className={form_globals.horizlist}>
|
||||
<span className={globals.icon}>image</span>
|
||||
<Field
|
||||
type="text"
|
||||
name="pfp"
|
||||
placeholder="https://example.com/"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
name="pfp"
|
||||
render={ErrorHandler}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
<Box properties={{ title: { text: "More" } }}>
|
||||
<span className={globals.text}>Clan preferences, et cetera.</span>
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={form_globals.verticalListCrunch}>
|
||||
<p className={globals.titleSmall}>Policies</p>
|
||||
<span className={globals.text}>How you want others to treat your characters.</span>
|
||||
</div>
|
||||
<Box
|
||||
properties={{
|
||||
class: form_globals.vertilist
|
||||
}}
|
||||
>
|
||||
<div className={globals.verticalListTop}>
|
||||
<p className={globals.titleSmall}>Fanart</p>
|
||||
<span className={globals.text}>Allow others to draw fanart of your characters.</span>
|
||||
<Field
|
||||
as="select"
|
||||
name="policies.fanart"
|
||||
placeholder="Troll"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
${form_globals.selectInput}
|
||||
`}
|
||||
>
|
||||
<option value="yes">Allow this</option>
|
||||
<option value="ask">Require asking</option>
|
||||
<option value="no">Don't do this</option>
|
||||
</Field>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
<p className={globals.titleSmall}>Fanart with other characters</p>
|
||||
<span className={globals.text}>Allow others to draw fanart of your characters with other characters.</span>
|
||||
<Field
|
||||
as="select"
|
||||
name="policies.fanartOthers"
|
||||
placeholder="Troll"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
${form_globals.selectInput}
|
||||
`}
|
||||
>
|
||||
<option value="yes">Allow this</option>
|
||||
<option value="ask">Require asking</option>
|
||||
<option value="no">Don't do this</option>
|
||||
</Field>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
<p className={globals.titleSmall}>Kinning</p>
|
||||
<span className={globals.text}>Allow others to kin your characters.</span>
|
||||
<Field
|
||||
as="select"
|
||||
name="policies.kinning"
|
||||
placeholder="Troll"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
${form_globals.selectInput}
|
||||
`}
|
||||
>
|
||||
<option value="yes">Allow this</option>
|
||||
<option value="ask">Require asking</option>
|
||||
<option value="no">Don't do this</option>
|
||||
</Field>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
<p className={globals.titleSmall}>Shipping</p>
|
||||
<Field
|
||||
as="select"
|
||||
name="policies.shipping"
|
||||
placeholder="Troll"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
${form_globals.selectInput}
|
||||
`}
|
||||
>
|
||||
<option value="yes">Allow this</option>
|
||||
<option value="ask">Require asking</option>
|
||||
<option value="no">Don't do this</option>
|
||||
</Field>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
<p className={globals.titleSmall}>Fanfiction</p>
|
||||
<Field
|
||||
as="select"
|
||||
name="policies.fanfiction"
|
||||
placeholder="Troll"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
${form_globals.selectInput}
|
||||
`}
|
||||
>
|
||||
<option value="yes">Allow this</option>
|
||||
<option value="ask">Require asking</option>
|
||||
<option value="no">Don't do this</option>
|
||||
</Field>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
name="policies"
|
||||
render={ErrorHandler}
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
<hr className={globals.invisep} />
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={form_globals.verticalListCrunch}>
|
||||
<p className={globals.titleSmall}>BG Image</p>
|
||||
<span className={globals.text}>An image that will display as a banner.</span>
|
||||
<span className={globals.text}>Supporter/moderator only.</span>
|
||||
</div>
|
||||
<div className={form_globals.horizlist}>
|
||||
<span className={globals.icon}>image</span>
|
||||
<Field
|
||||
type="text"
|
||||
name="bgimage"
|
||||
placeholder="https://example.com/"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
name="bgimage"
|
||||
render={ErrorHandler}
|
||||
/>
|
||||
</div>
|
||||
<hr className={globals.invisep} />
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={form_globals.verticalListCrunch}>
|
||||
<p className={globals.titleSmall}>CSS</p>
|
||||
<span className={globals.text}>Styling that is applied when your user page is accessed.</span>
|
||||
<span className={globals.text}>Supporter/moderator only.</span>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
<Field
|
||||
as="textarea"
|
||||
name="css"
|
||||
placeholder="CSS"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputMultiline}
|
||||
${form_globals.textLikeInputMono}
|
||||
`}
|
||||
/>
|
||||
<ErrorMessage
|
||||
name="css"
|
||||
render={ErrorHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr className={globals.invisep} />
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={form_globals.verticalListCrunch}>
|
||||
<p className={globals.titleSmall}>Code</p>
|
||||
<span className={globals.text}>
|
||||
The authentication code for your clan. If this is left blank, it will be randomly generated.
|
||||
</span>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
<div className={globals.horizontalListLeft}>
|
||||
<Field
|
||||
type="password"
|
||||
name="code"
|
||||
placeholder="Tim Sweeney"
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
name="code"
|
||||
render={ErrorHandler}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
${form_globals.verticalListCrunch}
|
||||
${form_globals.layoutAppearable}
|
||||
${values.code != initialValues.code && errors.code == null ? form_globals.appear : ""}
|
||||
`}
|
||||
>
|
||||
<span className={`${form_globals.render_info}`}>
|
||||
<span className={form_globals.iconSmall}>info</span>{" "}
|
||||
<span className={`${globals.text}`}>
|
||||
Make sure to make this somewhat secure! You can share this code with friends, but make sure you trust them.
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<Box properties={{ title: { text: "Form Settings" } }}>
|
||||
<div className={globals.horizontalListLeft}>
|
||||
<button
|
||||
type="reset"
|
||||
className={globals.button}
|
||||
disabled={isSubmitting}
|
||||
// onClick={() => resetForm()}
|
||||
>
|
||||
Reset Form
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className={globals.button}
|
||||
disabled={isSubmitting}
|
||||
// onClick={submitForm}
|
||||
>
|
||||
Submit Form
|
||||
</button>
|
||||
</div>
|
||||
<div className={globals.horizontalListLeft}>{submitError != null && submitError.length > 0 ? ErrorHandler(submitError) : ""}</div>
|
||||
<details>
|
||||
<summary>Advanced stuff</summary>
|
||||
<div className={globals.verticalListTop}>
|
||||
<details>
|
||||
<summary>Values (Advanced)</summary>
|
||||
<p className={`${globals.mono} ${globals.blockTextKeepTabs}`}>{JSON.stringify(values, null, 2)}</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Errors (Advanced)</summary>
|
||||
<p className={`${globals.mono} ${globals.blockTextKeepTabs}`}>{JSON.stringify(errors, null, 2)}</p>
|
||||
</details>
|
||||
<p className={globals.text}>If something is wrong, make sure to send these with your report!</p>
|
||||
</div>
|
||||
</details>
|
||||
</Box>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
}
|
1453
src/components/form/template/troll.tsx
Normal file
1453
src/components/form/template/troll.tsx
Normal file
File diff suppressed because it is too large
Load diff
90
src/components/template/credits.tsx
Normal file
90
src/components/template/credits.tsx
Normal file
|
@ -0,0 +1,90 @@
|
|||
import globals from "@/styles/global.module.css";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Credits() {
|
||||
return (
|
||||
<>
|
||||
<p className={globals.text}>
|
||||
TrollCall rev. 4 created with ❤️ by MeowcaTheoRange using{" "}
|
||||
<Link
|
||||
href="https://nextjs.org/"
|
||||
className={globals.link}
|
||||
>
|
||||
Next.js
|
||||
</Link>{" "}
|
||||
(Pages Router).
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
<b>trollcall.xyz</b> domain owned by [person who is not me].
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
The TrollCall name is derived from the original Hiveswap Troll
|
||||
Call. The name may be used in an entity context or a project
|
||||
context.
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
The textboxes found in the [INSERT PAGE HERE] are inspired by
|
||||
those from the game <b>Celeste</b>.
|
||||
</p>
|
||||
<p>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://github.com/MeowcaTheoRange/Fonts"
|
||||
>
|
||||
TrollCall Display
|
||||
</Link>{" "}
|
||||
font by MeowcaTheoRange.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://fonts.google.com/specimen/Space+Grotesk"
|
||||
>
|
||||
Space Grotesk
|
||||
</Link>{" "}
|
||||
font by Florian Karsten.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://fonts.google.com/specimen/Space+Mono"
|
||||
>
|
||||
Space Mono
|
||||
</Link>{" "}
|
||||
font by Colophon Foundry.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://fonts.google.com/specimen/Poppins"
|
||||
>
|
||||
Poppins
|
||||
</Link>{" "}
|
||||
font by Indian Type Foundry.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://fonts.google.com/specimen/Flow+Circular"
|
||||
>
|
||||
Flow Circular
|
||||
</Link>{" "}
|
||||
font by Dan Ross.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://www.creativefabrica.com/product/renogare/"
|
||||
>
|
||||
Renogare
|
||||
</Link>{" "}
|
||||
font by Deepak Dogra.
|
||||
</span>
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
Homestuck and HIVESWAP © Homestuck Inc.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
18
src/components/template/not-signed-in-clan.tsx
Normal file
18
src/components/template/not-signed-in-clan.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import globals from "@/styles/global.module.css";
|
||||
import Box from "../Box/Box";
|
||||
|
||||
export default function NotSignedInClan({ clan }: { clan: string }) {
|
||||
return (
|
||||
<Box properties={{ title: { text: "Not Signed In" } }}>
|
||||
<p className={globals.text}>
|
||||
You need to part of the {clan} clan to perform this action!
|
||||
</p>
|
||||
<a
|
||||
className={globals.link}
|
||||
href="/manage"
|
||||
>
|
||||
Join the {clan} clan
|
||||
</a>
|
||||
</Box>
|
||||
);
|
||||
}
|
24
src/components/template/not-signed-in.tsx
Normal file
24
src/components/template/not-signed-in.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import globals from "@/styles/global.module.css";
|
||||
import Box from "../Box/Box";
|
||||
|
||||
export default function NotSignedIn() {
|
||||
return (
|
||||
<Box properties={{ title: { text: "Not Signed In" } }}>
|
||||
<p className={globals.text}>
|
||||
You need to part a clan to perform this action!
|
||||
</p>
|
||||
<a
|
||||
className={globals.link}
|
||||
href="/manage"
|
||||
>
|
||||
Join a clan
|
||||
</a>
|
||||
<a
|
||||
className={globals.link}
|
||||
href="/add/clan"
|
||||
>
|
||||
Create a clan
|
||||
</a>
|
||||
</Box>
|
||||
);
|
||||
}
|
18
src/components/template/too-signed-in.tsx
Normal file
18
src/components/template/too-signed-in.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import globals from "@/styles/global.module.css";
|
||||
import Box from "../Box/Box";
|
||||
|
||||
export default function TooSignedIn() {
|
||||
return (
|
||||
<Box properties={{ title: { text: "Signed In" } }}>
|
||||
<p className={globals.text}>
|
||||
You can't be part a clan to perform this action!
|
||||
</p>
|
||||
<a
|
||||
className={globals.link}
|
||||
href="/manage"
|
||||
>
|
||||
Log out
|
||||
</a>
|
||||
</Box>
|
||||
);
|
||||
}
|
|
@ -26,3 +26,5 @@ if (process.env.NODE_ENV === "development") {
|
|||
}
|
||||
|
||||
export const mainDB = client.db(process.env.MONGODB_DATABASE_NAME);
|
||||
|
||||
export const firstDB = client.db("trollcall");
|
||||
|
|
|
@ -17,6 +17,7 @@ export async function ClanGET(
|
|||
: await getSingleClan(query);
|
||||
if (clan == null) return null;
|
||||
const serverClan = await ServerClanToClientClan({ ...clan });
|
||||
if (clan.flairs != null)
|
||||
serverClan.flairs = cutArray(
|
||||
await getManyFlairs(
|
||||
{ _id: { $in: clan.flairs } },
|
||||
|
|
|
@ -30,6 +30,7 @@ export async function TrollGET(
|
|||
});
|
||||
if (troll == null) return null;
|
||||
const serverTroll = await ServerTrollToClientTroll(troll);
|
||||
if (troll.flairs != null)
|
||||
serverTroll.flairs = cutArray(
|
||||
await getManyFlairs(
|
||||
{ _id: { $in: troll.flairs } },
|
||||
|
|
|
@ -14,6 +14,13 @@ export async function ServerClanToClientClan(
|
|||
return clientClan;
|
||||
}
|
||||
|
||||
export function ClientClanToSubmitClan(clientClan: ClientClan): SubmitClan {
|
||||
let serverClan: SubmitClan = {
|
||||
...clientClan
|
||||
};
|
||||
return serverClan;
|
||||
}
|
||||
|
||||
export function SubmitClanToServerClan(
|
||||
submitClan: Partial<SubmitClan>
|
||||
): Omit<Partial<ServerClan>, "_id"> {
|
||||
|
|
|
@ -10,7 +10,10 @@ export async function ServerTrollToClientTroll(
|
|||
|
||||
let clientTroll: Partial<ClientTroll> = {
|
||||
...sanitizedTroll,
|
||||
trueSign: TrueSign[serverTroll.trueSign],
|
||||
trueSign:
|
||||
serverTroll.trueSign != null
|
||||
? TrueSign[serverTroll.trueSign]
|
||||
: null,
|
||||
falseSign:
|
||||
serverTroll.falseSign != null
|
||||
? TrueSign[serverTroll.falseSign]
|
||||
|
@ -24,6 +27,24 @@ export async function ServerTrollToClientTroll(
|
|||
return clientTroll;
|
||||
}
|
||||
|
||||
export function ClientTrollToSubmitTroll(
|
||||
clientTroll: ClientTroll
|
||||
): SubmitTroll {
|
||||
let submitTroll: SubmitTroll = {
|
||||
...clientTroll,
|
||||
quirks: clientTroll.quirks
|
||||
? Object.entries(clientTroll.quirks)
|
||||
: undefined,
|
||||
trueSign: clientTroll.trueSign ? clientTroll.trueSign.name : undefined,
|
||||
falseSign: clientTroll.falseSign
|
||||
? clientTroll.falseSign.name
|
||||
: undefined,
|
||||
class: clientTroll.class ? clientTroll.class.name : undefined
|
||||
};
|
||||
|
||||
return submitTroll;
|
||||
}
|
||||
|
||||
export function SubmitTrollToServerTroll(
|
||||
submitTroll: Partial<SubmitTroll>
|
||||
): Omit<Partial<ServerTroll>, "_id"> {
|
||||
|
|
|
@ -1,48 +1,56 @@
|
|||
import Ads from "@/components/Ads/Ads";
|
||||
import Box from "@/components/Box/Box";
|
||||
import Nav from "@/components/Nav/Nav";
|
||||
import Credits from "@/components/template/credits";
|
||||
import "@/styles/_app.css";
|
||||
import "@/styles/global.module.css";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import { Color3 } from "@/types/assist/color";
|
||||
import { ThemeGet, ThemeGetOpt } from "@/types/generics";
|
||||
import AuthContext from "@/utility/react/AuthContext";
|
||||
import Themer, { ThemeModeContext } from "@/utility/react/Themer";
|
||||
import Themer, { ThemeModeContext, defaultTheme } from "@/utility/react/Themer";
|
||||
import { getCookies } from "cookies-next";
|
||||
import type { AppProps } from "next/app";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const [theme, setTheme] = useState([
|
||||
new Color3(0.9, 0.9, 0.8),
|
||||
new Color3(0.7, 0.7, 0.6),
|
||||
false
|
||||
] as [Color3, Color3, boolean?]);
|
||||
const [width, setWidth] = useState(768);
|
||||
const router = useRouter();
|
||||
const [theme, setTheme] = useState(defaultTheme as ThemeGet);
|
||||
function themeSetWrapper(themeSetter: ThemeGetOpt) {
|
||||
setTheme([
|
||||
themeSetter[0] ?? theme[0],
|
||||
themeSetter[1] ?? theme[1],
|
||||
themeSetter[2] ?? theme[2],
|
||||
themeSetter[3] ?? theme[3]
|
||||
] as ThemeGet);
|
||||
}
|
||||
const cookies = getCookies() as {
|
||||
TROLLCALL_NAME: string;
|
||||
TROLLCALL_CODE: string;
|
||||
};
|
||||
// useEffect(() => {
|
||||
// setTheme(defaultTheme);
|
||||
// }, [router.asPath]);
|
||||
return (
|
||||
<main className={"App" + (theme[2] ? " " + "inverted" : "")}>
|
||||
<main className={"App" + (theme[3] ? " " + "inverted" : "")}>
|
||||
<Themer
|
||||
pri={theme[0]}
|
||||
sec={theme[1]}
|
||||
inverted={theme[2]}
|
||||
inverted={theme[3]}
|
||||
/>
|
||||
<AuthContext.Provider value={cookies}>
|
||||
<Nav />
|
||||
<ThemeModeContext.Provider value={theme[2]}>
|
||||
<ThemeModeContext.Provider value={theme[3]}>
|
||||
<div
|
||||
className={"mainContent"}
|
||||
style={{
|
||||
maxWidth: width
|
||||
maxWidth: theme[2]
|
||||
}}
|
||||
>
|
||||
<Component
|
||||
{...pageProps}
|
||||
themerVars={[theme, setTheme]}
|
||||
widthVars={[width, setWidth]}
|
||||
themerVars={[theme, themeSetWrapper]}
|
||||
/>
|
||||
<Box
|
||||
properties={{
|
||||
|
@ -81,81 +89,7 @@ export default function App({ Component, pageProps }: AppProps) {
|
|||
</span>
|
||||
</Link>
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
TrollCall rev. 4 created by MeowcaTheoRange.
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
<b>trollcall.xyz</b> domain owned by Redact.
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
The TrollCall name is derived from the original
|
||||
Hiveswap Troll Call. The name may be used in an
|
||||
entity context or a project context.
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
The textboxes found in the [INSERT PAGE HERE]
|
||||
are inspired by those from the game{" "}
|
||||
<b>Celeste</b>.
|
||||
</p>
|
||||
<p>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://github.com/MeowcaTheoRange/Fonts"
|
||||
>
|
||||
TrollCall Display
|
||||
</Link>{" "}
|
||||
font by MeowcaTheoRange.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://fonts.google.com/specimen/Space+Grotesk"
|
||||
>
|
||||
Space Grotesk
|
||||
</Link>{" "}
|
||||
font by Florian Karsten.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://fonts.google.com/specimen/Space+Mono"
|
||||
>
|
||||
Space Mono
|
||||
</Link>{" "}
|
||||
font by Colophon Foundry.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://fonts.google.com/specimen/Poppins"
|
||||
>
|
||||
Poppins
|
||||
</Link>{" "}
|
||||
font by Indian Type Foundry.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://fonts.google.com/specimen/Flow+Circular"
|
||||
>
|
||||
Flow Circular
|
||||
</Link>{" "}
|
||||
font by Dan Ross.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://www.creativefabrica.com/product/renogare/"
|
||||
>
|
||||
Renogare
|
||||
</Link>{" "}
|
||||
font by Deepak Dogra.
|
||||
</span>
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
Homestuck and HIVESWAP © Homestuck Inc.
|
||||
</p>
|
||||
<Credits />
|
||||
</Box>
|
||||
</div>
|
||||
</ThemeModeContext.Provider>
|
||||
|
|
36
src/pages/add/clan/index.tsx
Normal file
36
src/pages/add/clan/index.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import Box from "@/components/Box/Box";
|
||||
import ClanFormTemplate from "@/components/form/template/clan";
|
||||
import TooSignedIn from "@/components/template/too-signed-in";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
import AuthContext from "@/utility/react/AuthContext";
|
||||
import { defaultTheme } from "@/utility/react/Themer";
|
||||
import { useRouter } from "next/router";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
export default function AddClan({
|
||||
themerVars: [theme, setTheme]
|
||||
}: {
|
||||
themerVars: ThemerGetSet;
|
||||
}) {
|
||||
const userCredentials = useContext(AuthContext);
|
||||
const router = useRouter();
|
||||
// Prevent hydration error. Nav Auth section is a client-rendered element.
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
setTheme(defaultTheme);
|
||||
}, []);
|
||||
return isClient && userCredentials.TROLLCALL_NAME == null ? (
|
||||
<>
|
||||
<Box properties={{ title: { text: "Add Clan" } }}>
|
||||
<span className={globals.text}>
|
||||
Create a clan to contribute to the site!
|
||||
</span>
|
||||
</Box>
|
||||
<ClanFormTemplate router={router} />
|
||||
</>
|
||||
) : (
|
||||
<TooSignedIn />
|
||||
);
|
||||
}
|
39
src/pages/add/troll/index.tsx
Normal file
39
src/pages/add/troll/index.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
import Box from "@/components/Box/Box";
|
||||
import TrollFormTemplate from "@/components/form/template/troll";
|
||||
import NotSignedIn from "@/components/template/not-signed-in";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
import AuthContext from "@/utility/react/AuthContext";
|
||||
import { defaultTheme } from "@/utility/react/Themer";
|
||||
import { useRouter } from "next/router";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
export default function AddTroll({
|
||||
themerVars: [theme, setTheme]
|
||||
}: {
|
||||
themerVars: ThemerGetSet;
|
||||
}) {
|
||||
const userCredentials = useContext(AuthContext);
|
||||
const router = useRouter();
|
||||
// Prevent hydration error. Nav Auth section is a client-rendered element.
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
setTheme(defaultTheme);
|
||||
}, []);
|
||||
return isClient && userCredentials.TROLLCALL_NAME != null ? (
|
||||
<>
|
||||
<Box properties={{ title: { text: "Add Troll" } }}>
|
||||
<span className={globals.text}>
|
||||
Add one of your own to TrollCall.
|
||||
</span>
|
||||
</Box>
|
||||
<TrollFormTemplate
|
||||
user={userCredentials}
|
||||
router={router}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<NotSignedIn />
|
||||
);
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
import { ClanGET } from "@/lib/trollcall/api/clan";
|
||||
import { getManyPagedClans } from "@/lib/trollcall/clan";
|
||||
import { ServerClanToClientClan } from "@/lib/trollcall/convert/clan";
|
||||
import { ServerFlairToClientFlair } from "@/lib/trollcall/convert/flair";
|
||||
import { getManyFlairs } from "@/lib/trollcall/flair";
|
||||
import { cutArray } from "@/lib/trollcall/utility/merge";
|
||||
import { ClientClan } from "@/types/clan";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
|
@ -19,12 +16,12 @@ export default async function handler(
|
|||
async (clan: any) => {
|
||||
let thisClan = await ServerClanToClientClan(clan);
|
||||
thisClan = (await ClanGET(null, clan)) as ClientClan;
|
||||
thisClan.flairs = cutArray(
|
||||
await getManyFlairs(
|
||||
{ _id: { $in: clan.flairs } },
|
||||
ServerFlairToClientFlair
|
||||
)
|
||||
);
|
||||
// if(thisClan.flairs != null) thisClan.flairs = cutArray(
|
||||
// await getManyFlairs(
|
||||
// { _id: { $in: clan.flairs } },
|
||||
// ServerFlairToClientFlair
|
||||
// )
|
||||
// );
|
||||
return thisClan;
|
||||
},
|
||||
5,
|
||||
|
|
|
@ -55,10 +55,15 @@ export default async function handler(
|
|||
if (serverClan.code == null)
|
||||
serverClan.code = checkExistingClan.code || nanoid(16);
|
||||
|
||||
const currentcode = serverClan.code;
|
||||
|
||||
// Encrypt code lole
|
||||
serverClan.code = await hash(serverClan.code);
|
||||
|
||||
if (!compareLevels(getLevel(checkExistingClan), "SUPPORTER")) {
|
||||
if (
|
||||
serverClan.flairs != null &&
|
||||
!compareLevels(getLevel(checkExistingClan), "SUPPORTER")
|
||||
) {
|
||||
serverClan.bgimage = null;
|
||||
serverClan.css = null;
|
||||
}
|
||||
|
@ -73,11 +78,7 @@ export default async function handler(
|
|||
path: "/",
|
||||
maxAge: 31540000
|
||||
}),
|
||||
serialize("TROLLCALL_CODE", newClan.code, {
|
||||
path: "/",
|
||||
maxAge: 31540000
|
||||
}),
|
||||
serialize("TROLLCALL_PFP", newClan.pfp ?? "", {
|
||||
serialize("TROLLCALL_CODE", currentcode, {
|
||||
path: "/",
|
||||
maxAge: 31540000
|
||||
})
|
||||
|
|
|
@ -33,11 +33,18 @@ export default async function handler(
|
|||
>;
|
||||
if (serverClan.code == null) serverClan.code = nanoid(16);
|
||||
|
||||
// Encrypt code lole
|
||||
serverClan.code = hash(serverClan.code).toString();
|
||||
const currentcode = serverClan.code;
|
||||
|
||||
if (!compareLevels(getLevel(serverClan), "SUPPORTER"))
|
||||
// Encrypt code lole
|
||||
serverClan.code = await hash(serverClan.code).toString();
|
||||
|
||||
if (
|
||||
serverClan.flairs != null &&
|
||||
!compareLevels(getLevel(serverClan), "SUPPORTER")
|
||||
) {
|
||||
serverClan.bgimage = null;
|
||||
serverClan.css = null;
|
||||
}
|
||||
const newClan = await createClan(serverClan);
|
||||
if (newClan == null) return res.status(503).end();
|
||||
// Give cookies
|
||||
|
@ -46,11 +53,7 @@ export default async function handler(
|
|||
path: "/",
|
||||
maxAge: 31540000
|
||||
}),
|
||||
serialize("TROLLCALL_CODE", newClan.code, {
|
||||
path: "/",
|
||||
maxAge: 31540000
|
||||
}),
|
||||
serialize("TROLLCALL_PFP", newClan.pfp ?? "", {
|
||||
serialize("TROLLCALL_CODE", currentcode, {
|
||||
path: "/",
|
||||
maxAge: 31540000
|
||||
})
|
||||
|
|
143
src/pages/api/convert/index.ts
Normal file
143
src/pages/api/convert/index.ts
Normal file
|
@ -0,0 +1,143 @@
|
|||
// import { Filter, cursorToArray } from "@/lib/db/crud";
|
||||
// import { firstDB, mainDB } from "@/lib/db/mongodb";
|
||||
// import { ServerClan } from "@/types/clan";
|
||||
// import { ServerTroll } from "@/types/troll";
|
||||
// import { AdaptivePossessive } from "@/utility/language";
|
||||
// import { hash } from "argon2";
|
||||
// import { Sort } from "mongodb";
|
||||
// import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
// export function readMany(
|
||||
// collection: string,
|
||||
// find: Filter<Document>,
|
||||
// sort: Sort
|
||||
// ) {
|
||||
// const selectedCollection = firstDB.collection(collection);
|
||||
// return selectedCollection.find(find).sort(sort);
|
||||
// }
|
||||
|
||||
// export async function addMany(
|
||||
// collection: string,
|
||||
// items: { [key: string]: any }[]
|
||||
// ) {
|
||||
// const selectedCollection = mainDB.collection(collection);
|
||||
// return await selectedCollection.insertMany(items, { ordered: true });
|
||||
// }
|
||||
|
||||
// export default async function handler(
|
||||
// req: NextApiRequest,
|
||||
// res: NextApiResponse
|
||||
// ) {
|
||||
// const { body, cookies, query, method } = req;
|
||||
// if (method === "GET") {
|
||||
// const allUsers: ServerClan[] = await cursorToArray(
|
||||
// readMany("users", {}, { "name": 1 }),
|
||||
// async x => {
|
||||
// const sc: ServerClan = {
|
||||
// _id: x._id,
|
||||
// updatedDate: new Date(x.updatedDate),
|
||||
// displayName: AdaptivePossessive(x.name, "Clan"),
|
||||
// url: x.url,
|
||||
// color: x.color,
|
||||
// pfp: x.pfp,
|
||||
// bgimage: null,
|
||||
// css: null,
|
||||
// name: x.name,
|
||||
// members: [
|
||||
// {
|
||||
// name: x.name,
|
||||
// pronouns: [["they", "them", "theirs"]]
|
||||
// }
|
||||
// ],
|
||||
// description: x.description,
|
||||
// policies: {
|
||||
// fanart: "no",
|
||||
// fanartOthers: "no",
|
||||
// kinning: "no",
|
||||
// shipping: "no",
|
||||
// fanfiction: "no"
|
||||
// },
|
||||
// code: await hash(x.code),
|
||||
// flairs: []
|
||||
// };
|
||||
// return sc;
|
||||
// }
|
||||
// );
|
||||
// let allTrolls: ServerTroll[] = await cursorToArray(
|
||||
// readMany("trolls", {}, { "name.0": 1 }),
|
||||
// async x => {
|
||||
// const st: ServerTroll = {
|
||||
// _id: x._id,
|
||||
// policies: x.policies,
|
||||
// updatedDate: x.updatedDate,
|
||||
// gender: x.gender,
|
||||
// facts: x.facts,
|
||||
// trueSign: x.trueSign,
|
||||
// falseSign: x.falseSign,
|
||||
// class: x.class,
|
||||
// username: x.username,
|
||||
// textColor: x.textColor,
|
||||
// pageColor: null,
|
||||
// quotes: [],
|
||||
// species: x.species,
|
||||
// name: x.name,
|
||||
// pronouns: x.pronouns,
|
||||
// description: x.description,
|
||||
// flairs: [],
|
||||
// pronunciation: x.pronunciation,
|
||||
// preferences: x.preferences,
|
||||
// quirks: x.quirks,
|
||||
// height: x.height,
|
||||
// age: Math.floor(x.age * 2.1666 * 10) / 10,
|
||||
// images: [x.image],
|
||||
// owner: x.owners[0]
|
||||
// };
|
||||
// return st;
|
||||
// }
|
||||
// );
|
||||
// const resultClans = await addMany("clans", allUsers);
|
||||
// const resultTrolls = await addMany("trolls", allTrolls);
|
||||
// return res
|
||||
// .status(200)
|
||||
// .json({ "users": resultClans, "trolls": resultTrolls });
|
||||
// } else return res.status(405).end();
|
||||
// }
|
||||
|
||||
// /*
|
||||
// type ServerTroll = Omit<{
|
||||
// policies?: {
|
||||
// fanart: "yes" | "ask" | "no" | null;
|
||||
// fanartOthers: "yes" | "ask" | "no" | null;
|
||||
// kinning: "yes" | "ask" | "no" | null;
|
||||
// shipping: "yes" | "ask" | "no" | null;
|
||||
// fanfiction: "yes" | "ask" | "no" | null;
|
||||
// } | null | undefined;
|
||||
// updatedDate?: Date | undefined;
|
||||
// gender?: string | undefined;
|
||||
// facts?: string[] | undefined;
|
||||
// trueSign?: string | null | undefined;
|
||||
// falseSign?: string | null | undefined;
|
||||
// class?: string | null | undefined;
|
||||
// username?: string | undefined;
|
||||
// textColor?: yup.Maybe<[number, number, number] | undefined>;
|
||||
// pageColor?: yup.Maybe<[number, number, number] | undefined>;
|
||||
// quotes?: string[] | undefined;
|
||||
// species?: yup.Maybe<string | undefined>;
|
||||
// name: [string, string];
|
||||
// pronouns: [string, string, string][];
|
||||
// description: string;
|
||||
// flairs: ObjectId[];
|
||||
// pronunciation: [string, string];
|
||||
// preferences: {
|
||||
// love?: string[] | undefined;
|
||||
// hate?: string[] | undefined;
|
||||
// };
|
||||
// quirks: AnyPresentValue;
|
||||
// height: number;
|
||||
// age: number;
|
||||
// images: string[];
|
||||
// owner: ObjectId;
|
||||
// }, "_id"> & {
|
||||
// _id: ObjectId;
|
||||
// }
|
||||
// */
|
46
src/pages/api/test/signin/index.ts
Normal file
46
src/pages/api/test/signin/index.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { getSingleClan } from "@/lib/trollcall/clan";
|
||||
import { compareCredentials } from "@/lib/trollcall/perms";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { body, cookies, query, method } = req;
|
||||
if (method === "GET") {
|
||||
if (cookies.TROLLCALL_NAME == null)
|
||||
return res
|
||||
.status(403)
|
||||
.send("Can't authenticate (name is null, are you signed out?)");
|
||||
if (cookies.TROLLCALL_CODE == null)
|
||||
return res
|
||||
.status(403)
|
||||
.send("Can't authenticate (code is null, are you signed out?)");
|
||||
const checkClan = await getSingleClan({
|
||||
name: cookies.TROLLCALL_NAME
|
||||
});
|
||||
if (checkClan == null)
|
||||
return res
|
||||
.status(404)
|
||||
.send(
|
||||
"Can't authenticate (can't find clan \"" +
|
||||
cookies.TROLLCALL_NAME +
|
||||
'")'
|
||||
);
|
||||
if (await compareCredentials(checkClan, cookies))
|
||||
return res
|
||||
.status(200)
|
||||
.send(
|
||||
"Authenticated as " + checkClan.displayName ??
|
||||
checkClan.name
|
||||
);
|
||||
else
|
||||
return res
|
||||
.status(403)
|
||||
.send(
|
||||
"Can't authenticate (trying as " +
|
||||
(checkClan.displayName ?? checkClan.name) +
|
||||
", check code?)"
|
||||
);
|
||||
} else return res.status(405).end();
|
||||
}
|
|
@ -21,6 +21,7 @@ export default async function handler(
|
|||
thisTroll.owner = (await ClanGET({
|
||||
_id: troll.owner
|
||||
})) as ClientClan;
|
||||
if (troll.flairs != null)
|
||||
thisTroll.flairs = cutArray(
|
||||
await getManyFlairs(
|
||||
{ _id: { $in: troll.flairs } },
|
||||
|
|
|
@ -27,6 +27,7 @@ export default async function handler(
|
|||
async (troll: any) => {
|
||||
const thisTroll = await ServerTrollToClientTroll(troll);
|
||||
thisTroll.owner = clientClan;
|
||||
if (troll.flairs != null)
|
||||
thisTroll.flairs = cutArray(
|
||||
await getManyFlairs(
|
||||
{ _id: { $in: troll.flairs } },
|
||||
|
|
|
@ -34,6 +34,7 @@ export default async function handler(
|
|||
stripUnknown: true
|
||||
})) as Partial<SubmitTroll>;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return res.status(400).send(err);
|
||||
}
|
||||
const checkClan = await getSingleClan({
|
||||
|
|
|
@ -3,13 +3,17 @@ import ClanCard from "@/components/cards/ClanCard/ClanCard";
|
|||
import TrollCard from "@/components/cards/TrollCard/TrollCard";
|
||||
import TrollSkeleton from "@/components/cards/TrollCard/TrollSkeleton";
|
||||
import { ClanGET } from "@/lib/trollcall/api/clan";
|
||||
import { cutObject } from "@/lib/trollcall/utility/merge";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import { Color3 } from "@/types/assist/color";
|
||||
import { ClientClan } from "@/types/clan";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
import { ClientTroll } from "@/types/troll";
|
||||
import AuthContext from "@/utility/react/AuthContext";
|
||||
import Conditional from "@/utility/react/Conditional";
|
||||
import { GetServerSideProps, GetStaticPropsContext } from "next";
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
export default function Index({
|
||||
themerVars: [theme, setTheme],
|
||||
|
@ -18,26 +22,39 @@ export default function Index({
|
|||
themerVars: ThemerGetSet;
|
||||
clan: ClientClan;
|
||||
}) {
|
||||
// Get user creds
|
||||
const userCredentials = useContext(AuthContext);
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
useEffect(() => setIsClient(true), []);
|
||||
|
||||
// Get user trolls
|
||||
const [fetchedTrolls, setFetchedTrolls] = useState<ClientTroll[] | null>(
|
||||
null
|
||||
);
|
||||
useEffect(() => {
|
||||
async function getTroll() {
|
||||
const res = await fetch("/api/troll/" + clan.name + "/...");
|
||||
|
||||
async function getTroll(page?: number) {
|
||||
const res = await fetch(
|
||||
page ? "/api/troll/.../" + page : "/api/troll/..."
|
||||
);
|
||||
const json = await res.json();
|
||||
setFetchedTrolls(json);
|
||||
}
|
||||
getTroll();
|
||||
useEffect(() => {
|
||||
getTroll(0);
|
||||
const color = clan.color?.map(x => x / 255) as [number, number, number];
|
||||
if (color != null)
|
||||
setTheme([new Color3(...color), new Color3(...color).darken(50)]);
|
||||
setTheme([
|
||||
new Color3(...color),
|
||||
new Color3(...color).darken(50),
|
||||
768
|
||||
]);
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<Conditional condition={clan.bgimage != null}>
|
||||
<style>{`
|
||||
main.App {
|
||||
background-image: linear-gradient(#0008, #0008), url(${clan.bgimage});
|
||||
background-image: var(--darken), url(${clan.bgimage}) !important;
|
||||
background-size: cover;
|
||||
background-repeat: repeat;
|
||||
background-position: 50% 50%;
|
||||
|
@ -49,10 +66,34 @@ ${clan.css ?? ""}
|
|||
clan={clan}
|
||||
link={false}
|
||||
/>
|
||||
{isClient && userCredentials.TROLLCALL_NAME == clan.name ? (
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text: "Admin"
|
||||
},
|
||||
theme: theme[1]
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
Well, it seems you can manage{" "}
|
||||
<b>{clan.displayName ?? clan.name}</b>! Have this menu.
|
||||
</p>
|
||||
<Link
|
||||
className={globals.linkButton}
|
||||
href={`/edit/clan/${clan.name}/`}
|
||||
>
|
||||
Edit Clan
|
||||
</Link>
|
||||
</Box>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text: "Clan Trolls",
|
||||
link: "/troll/s/" + clan.name,
|
||||
small: true
|
||||
}
|
||||
}}
|
||||
|
@ -87,7 +128,7 @@ export const getServerSideProps: GetServerSideProps<{
|
|||
};
|
||||
return {
|
||||
props: {
|
||||
clan
|
||||
clan: cutObject(clan)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
76
src/pages/clan/s/index.tsx
Normal file
76
src/pages/clan/s/index.tsx
Normal file
|
@ -0,0 +1,76 @@
|
|||
import Box from "@/components/Box/Box";
|
||||
import ClanCard from "@/components/cards/ClanCard/ClanCard";
|
||||
import ClanSkeleton from "@/components/cards/ClanCard/ClanSkeleton";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import "@/styles/index.module.css";
|
||||
import { ClientClan } from "@/types/clan";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
import { defaultTheme } from "@/utility/react/Themer";
|
||||
import { getCookies } from "cookies-next";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
getCookies();
|
||||
|
||||
export default function Index({
|
||||
themerVars: [theme, setTheme]
|
||||
}: {
|
||||
themerVars: ThemerGetSet;
|
||||
}) {
|
||||
const [fetchedClans, setFetchedClans] = useState<ClientClan[]>([]);
|
||||
const [clanPageNum, setClanPageNum] = useState(0);
|
||||
const [noMore, setNoMore] = useState(false);
|
||||
async function getClan(page?: number) {
|
||||
const res = await fetch(
|
||||
page ? "/api/clan/.../" + page : "/api/clan/..."
|
||||
);
|
||||
const json = await res.json();
|
||||
setFetchedClans(fetchedClans.concat(json));
|
||||
setNoMore(json.length < 5);
|
||||
}
|
||||
useEffect(() => {
|
||||
getClan(clanPageNum);
|
||||
setTheme(defaultTheme);
|
||||
}, [clanPageNum]);
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text: "Explore clans"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className={globals.text}>
|
||||
See a list of all the clans on TrollCall.
|
||||
</span>
|
||||
</Box>
|
||||
<Box>
|
||||
{fetchedClans.length <= 0 ? (
|
||||
<>
|
||||
<ClanSkeleton />
|
||||
<ClanSkeleton />
|
||||
<ClanSkeleton />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{fetchedClans.map((clan: ClientClan, idx) => (
|
||||
<ClanCard
|
||||
clan={clan}
|
||||
key={idx + "clan"}
|
||||
/>
|
||||
))}
|
||||
<button
|
||||
className={globals.button}
|
||||
onClick={() => {
|
||||
setClanPageNum(clanPageNum + 1);
|
||||
}}
|
||||
disabled={noMore}
|
||||
>
|
||||
Load more
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
65
src/pages/edit/clan/[clan]/index.tsx
Normal file
65
src/pages/edit/clan/[clan]/index.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
import Box from "@/components/Box/Box";
|
||||
import ClanFormTemplate from "@/components/form/template/clan";
|
||||
import NotSignedInClan from "@/components/template/not-signed-in-clan";
|
||||
import { ClanGET } from "@/lib/trollcall/api/clan";
|
||||
import { ClientClanToSubmitClan } from "@/lib/trollcall/convert/clan";
|
||||
import { cutObject } from "@/lib/trollcall/utility/merge";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import { ClientClan } from "@/types/clan";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
import AuthContext from "@/utility/react/AuthContext";
|
||||
import { defaultTheme } from "@/utility/react/Themer";
|
||||
import { GetServerSideProps, GetStaticPropsContext } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
export default function AddTroll({
|
||||
themerVars: [theme, setTheme],
|
||||
clan
|
||||
}: {
|
||||
themerVars: ThemerGetSet;
|
||||
clan: ClientClan;
|
||||
}) {
|
||||
const userCredentials = useContext(AuthContext);
|
||||
const router = useRouter();
|
||||
// Prevent hydration error. Nav Auth section is a client-rendered element.
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
setTheme(defaultTheme);
|
||||
console.log(clan);
|
||||
}, []);
|
||||
return isClient && userCredentials.TROLLCALL_NAME == clan.name ? (
|
||||
<>
|
||||
<Box properties={{ title: { text: "Edit Clan" } }}>
|
||||
<span className={globals.text}>Tweak your clan.</span>
|
||||
</Box>
|
||||
<ClanFormTemplate
|
||||
router={router}
|
||||
method="PUT"
|
||||
onSubmitURI={`/api/clan/${clan.name}`}
|
||||
initialValues={ClientClanToSubmitClan(clan)}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<NotSignedInClan clan={clan.displayName ?? clan.name} />
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<{
|
||||
clan: ClientClan;
|
||||
}> = async (context: GetStaticPropsContext) => {
|
||||
if (context.params?.clan == null) return { notFound: true };
|
||||
const clan = await ClanGET({
|
||||
name: context.params?.clan
|
||||
});
|
||||
if (clan == null)
|
||||
return {
|
||||
notFound: true
|
||||
};
|
||||
return {
|
||||
props: {
|
||||
clan: cutObject(clan)
|
||||
}
|
||||
};
|
||||
};
|
77
src/pages/edit/troll/[clan]/[troll]/index.tsx
Normal file
77
src/pages/edit/troll/[clan]/[troll]/index.tsx
Normal file
|
@ -0,0 +1,77 @@
|
|||
import Box from "@/components/Box/Box";
|
||||
import TrollFormTemplate from "@/components/form/template/troll";
|
||||
import NotSignedInClan from "@/components/template/not-signed-in-clan";
|
||||
import { TrollGET } from "@/lib/trollcall/api/troll";
|
||||
import { getSingleClan } from "@/lib/trollcall/clan";
|
||||
import { ClientTrollToSubmitTroll } from "@/lib/trollcall/convert/troll";
|
||||
import { cutObject } from "@/lib/trollcall/utility/merge";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
import { ClientTroll } from "@/types/troll";
|
||||
import AuthContext from "@/utility/react/AuthContext";
|
||||
import { defaultTheme } from "@/utility/react/Themer";
|
||||
import { GetServerSideProps, GetStaticPropsContext } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
export default function AddTroll({
|
||||
themerVars: [theme, setTheme],
|
||||
troll
|
||||
}: {
|
||||
themerVars: ThemerGetSet;
|
||||
troll: ClientTroll;
|
||||
}) {
|
||||
const userCredentials = useContext(AuthContext);
|
||||
const router = useRouter();
|
||||
// Prevent hydration error. Nav Auth section is a client-rendered element.
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
setTheme(defaultTheme);
|
||||
console.log(troll);
|
||||
}, []);
|
||||
return isClient && userCredentials.TROLLCALL_NAME == troll.owner.name ? (
|
||||
<>
|
||||
<Box properties={{ title: { text: "Edit Troll" } }}>
|
||||
<span className={globals.text}>Edit one of your trolls.</span>
|
||||
</Box>
|
||||
<TrollFormTemplate
|
||||
user={userCredentials}
|
||||
router={router}
|
||||
method="PUT"
|
||||
onSubmitURI={`/api/troll/${troll.owner.name}/${troll.name[0]}`}
|
||||
initialValues={ClientTrollToSubmitTroll(troll)}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<NotSignedInClan clan={troll.owner.displayName ?? troll.owner.name} />
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<{
|
||||
troll: ClientTroll;
|
||||
}> = async (context: GetStaticPropsContext) => {
|
||||
if (context.params?.clan == null) return { notFound: true };
|
||||
const serverClan = await getSingleClan({
|
||||
name: context.params?.clan
|
||||
});
|
||||
if (serverClan == null)
|
||||
return {
|
||||
notFound: true
|
||||
};
|
||||
if (context.params?.troll == null) return { notFound: true };
|
||||
const troll = await TrollGET(
|
||||
{ name: context.params.troll },
|
||||
null,
|
||||
serverClan
|
||||
);
|
||||
if (troll == null)
|
||||
return {
|
||||
notFound: true
|
||||
};
|
||||
return {
|
||||
props: {
|
||||
troll: cutObject(troll)
|
||||
}
|
||||
};
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
import Box from "@/components/Box/Box";
|
||||
import Credits from "@/components/template/credits";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import "@/styles/index.module.css";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
|
@ -451,80 +452,7 @@ export default function Index({
|
|||
}
|
||||
}}
|
||||
>
|
||||
<p className={globals.text}>
|
||||
TrollCall rev. 4 created by MeowcaTheoRange.
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
<b>trollcall.xyz</b> domain owned by Redact.
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
The TrollCall name is derived from the original Hiveswap
|
||||
Troll Call. The name may be used in an entity context or a
|
||||
project context.
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
The textboxes found in the [INSERT PAGE HERE] are inspired
|
||||
by those from the game <b>Celeste</b>.
|
||||
</p>
|
||||
<p>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://github.com/MeowcaTheoRange/Fonts"
|
||||
>
|
||||
TrollCall Display
|
||||
</Link>{" "}
|
||||
font by MeowcaTheoRange.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://fonts.google.com/specimen/Space+Grotesk"
|
||||
>
|
||||
Space Grotesk
|
||||
</Link>{" "}
|
||||
font by Florian Karsten.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://fonts.google.com/specimen/Space+Mono"
|
||||
>
|
||||
Space Mono
|
||||
</Link>{" "}
|
||||
font by Colophon Foundry.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://fonts.google.com/specimen/Poppins"
|
||||
>
|
||||
Poppins
|
||||
</Link>{" "}
|
||||
font by Indian Type Foundry.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://fonts.google.com/specimen/Flow+Circular"
|
||||
>
|
||||
Flow Circular
|
||||
</Link>{" "}
|
||||
font by Dan Ross.
|
||||
</span>
|
||||
<span className={globals.blockText}>
|
||||
<Link
|
||||
className={globals.link}
|
||||
href="https://www.creativefabrica.com/product/renogare/"
|
||||
>
|
||||
Renogare
|
||||
</Link>{" "}
|
||||
font by Deepak Dogra.
|
||||
</span>
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
Homestuck and HIVESWAP © Homestuck Inc.
|
||||
</p>
|
||||
<Credits />
|
||||
</Box>
|
||||
<Box properties={{ title: { text: "Contact Me" } }}>
|
||||
<p className={globals.text}>
|
||||
|
|
|
@ -33,7 +33,7 @@ export default function Index({
|
|||
return (
|
||||
<>
|
||||
<Box properties={{}}>
|
||||
<p className={globals.iconText}>
|
||||
<p className={`${globals.iconText} ${globals.forceLTR}`}>
|
||||
<span className={globals.icon}>arrow_back</span>
|
||||
<Link
|
||||
className={globals.link}
|
||||
|
@ -100,7 +100,7 @@ export const getStaticPaths: GetStaticPaths = () => {
|
|||
name: aspect.name
|
||||
}
|
||||
})),
|
||||
fallback: true
|
||||
fallback: false
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function Index({
|
|||
return (
|
||||
<>
|
||||
<Box properties={{}}>
|
||||
<p className={globals.iconText}>
|
||||
<p className={`${globals.iconText} ${globals.forceLTR}`}>
|
||||
<span className={globals.icon}>arrow_back</span>
|
||||
<Link
|
||||
className={globals.link}
|
||||
|
|
|
@ -33,7 +33,7 @@ export default function Index({
|
|||
return (
|
||||
<>
|
||||
<Box properties={{}}>
|
||||
<p className={globals.iconText}>
|
||||
<p className={`${globals.iconText} ${globals.forceLTR}`}>
|
||||
<span className={globals.icon}>arrow_back</span>
|
||||
<Link
|
||||
className={globals.link}
|
||||
|
@ -99,7 +99,7 @@ export const getStaticPaths: GetStaticPaths = () => {
|
|||
name: color.name
|
||||
}
|
||||
})),
|
||||
fallback: true
|
||||
fallback: false
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function Index({
|
|||
return (
|
||||
<>
|
||||
<Box properties={{}}>
|
||||
<p className={globals.iconText}>
|
||||
<p className={`${globals.iconText} ${globals.forceLTR}`}>
|
||||
<span className={globals.icon}>arrow_back</span>
|
||||
<Link
|
||||
className={globals.link}
|
||||
|
|
|
@ -15,8 +15,8 @@ export default function Index({
|
|||
useEffect(() => setTheme(hiveswapTheme), []);
|
||||
return (
|
||||
<>
|
||||
<Box properties={{}}>
|
||||
<p className={globals.iconText}>
|
||||
<Box>
|
||||
<p className={`${globals.iconText} ${globals.forceLTR}`}>
|
||||
<span className={globals.icon}>arrow_back</span>
|
||||
<span className={globals.text}>hiveswap</span>
|
||||
</p>
|
||||
|
|
|
@ -34,7 +34,7 @@ export default function Index({
|
|||
return (
|
||||
<>
|
||||
<Box properties={{}}>
|
||||
<p className={globals.iconText}>
|
||||
<p className={`${globals.iconText} ${globals.forceLTR}`}>
|
||||
<span className={globals.icon}>arrow_back</span>
|
||||
<Link
|
||||
className={globals.link}
|
||||
|
@ -110,7 +110,7 @@ export const getStaticPaths: GetStaticPaths = () => {
|
|||
name: sway.name
|
||||
}
|
||||
})),
|
||||
fallback: true
|
||||
fallback: false
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function Index({
|
|||
return (
|
||||
<>
|
||||
<Box properties={{}}>
|
||||
<p className={globals.iconText}>
|
||||
<p className={`${globals.iconText} ${globals.forceLTR}`}>
|
||||
<span className={globals.icon}>arrow_back</span>
|
||||
<Link
|
||||
className={globals.link}
|
||||
|
|
|
@ -31,7 +31,7 @@ export default function Index({
|
|||
return (
|
||||
<>
|
||||
<Box properties={{}}>
|
||||
<p className={globals.iconText}>
|
||||
<p className={`${globals.iconText} ${globals.forceLTR}`}>
|
||||
<span className={globals.icon}>arrow_back</span>
|
||||
<Link
|
||||
className={globals.link}
|
||||
|
@ -147,7 +147,7 @@ export const getStaticPaths: GetStaticPaths = () => {
|
|||
name: trueSign.name
|
||||
}
|
||||
})),
|
||||
fallback: true
|
||||
fallback: false
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ export default function Index({
|
|||
return (
|
||||
<>
|
||||
<Box properties={{}}>
|
||||
<p className={globals.iconText}>
|
||||
<p className={`${globals.iconText} ${globals.forceLTR}`}>
|
||||
<span className={globals.icon}>arrow_back</span>
|
||||
<Link
|
||||
className={globals.link}
|
||||
|
|
|
@ -21,23 +21,25 @@ export default function Index({
|
|||
}: {
|
||||
themerVars: ThemerGetSet;
|
||||
}) {
|
||||
const [fetchedTrolls, setFetchedTrolls] = useState<ClientTroll[] | null>(
|
||||
null
|
||||
const [fetchedTrolls, setFetchedTrolls] = useState<ClientTroll[]>([]);
|
||||
const [fetchedClans, setFetchedClans] = useState<ClientClan[]>([]);
|
||||
async function getTroll(page?: number) {
|
||||
const res = await fetch(
|
||||
page ? "/api/troll/.../" + page : "/api/troll/..."
|
||||
);
|
||||
const [fetchedClans, setFetchedClans] = useState<ClientClan[] | null>(null);
|
||||
useEffect(() => {
|
||||
async function getTroll() {
|
||||
const res = await fetch("/api/troll/...");
|
||||
const json = await res.json();
|
||||
setFetchedTrolls(json);
|
||||
}
|
||||
getTroll();
|
||||
async function getClan() {
|
||||
const res = await fetch("/api/clan/...");
|
||||
async function getClan(page?: number) {
|
||||
const res = await fetch(
|
||||
page ? "/api/clan/.../" + page : "/api/clan/..."
|
||||
);
|
||||
const json = await res.json();
|
||||
setFetchedClans(json);
|
||||
}
|
||||
getClan();
|
||||
useEffect(() => {
|
||||
getTroll(0);
|
||||
getClan(0);
|
||||
setTheme(defaultTheme);
|
||||
}, []);
|
||||
return (
|
||||
|
@ -73,7 +75,8 @@ export default function Index({
|
|||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text: "List of characters",
|
||||
text: "Current characters",
|
||||
link: "/troll/s",
|
||||
small: true
|
||||
}
|
||||
}}
|
||||
|
@ -96,7 +99,8 @@ export default function Index({
|
|||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text: "List of clans",
|
||||
text: "Current clans",
|
||||
link: "/clan/s",
|
||||
small: true
|
||||
}
|
||||
}}
|
||||
|
|
9
src/pages/manage/index.module.css
Normal file
9
src/pages/manage/index.module.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
.showOnHover {
|
||||
filter: blur(8px);
|
||||
transition: filter 1s, user-select 1s step-end;
|
||||
user-select: none;
|
||||
}
|
||||
.showOnHover:hover {
|
||||
filter: none;
|
||||
user-select: text;
|
||||
}
|
145
src/pages/manage/index.tsx
Normal file
145
src/pages/manage/index.tsx
Normal file
|
@ -0,0 +1,145 @@
|
|||
import Box from "@/components/Box/Box";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import form_globals from "@/styles/global_form.module.css";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
import AuthContext from "@/utility/react/AuthContext";
|
||||
import { defaultTheme } from "@/utility/react/Themer";
|
||||
import { deleteCookie, getCookies, setCookie } from "cookies-next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
export default function Manage({
|
||||
themerVars: [theme, setTheme]
|
||||
}: {
|
||||
themerVars: ThemerGetSet;
|
||||
}) {
|
||||
const userCredentials = useContext(AuthContext);
|
||||
const nameInput = useRef<HTMLInputElement>(null);
|
||||
const codeInput = useRef<HTMLInputElement>(null);
|
||||
const router = useRouter();
|
||||
// Prevent hydration error. Nav Auth section is a client-rendered element.
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
setTheme(defaultTheme);
|
||||
}, []);
|
||||
const cookies = getCookies() as {
|
||||
TROLLCALL_NAME: string;
|
||||
TROLLCALL_CODE: string;
|
||||
};
|
||||
|
||||
const [statusHolder, setStatusHolder] = useState("");
|
||||
return isClient && userCredentials.TROLLCALL_NAME != null ? (
|
||||
<>
|
||||
<Box properties={{ title: { text: "Sign out?" } }}>
|
||||
<p className={globals.text}>
|
||||
Are you sure you want to sign out of TrollCall?
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
Make sure to remember your credentials!
|
||||
</p>
|
||||
<details>
|
||||
<summary>
|
||||
<span className={globals.text}>Show login</span>
|
||||
</summary>
|
||||
<p className={globals.text}>
|
||||
NAME:{" "}
|
||||
<span className={globals.mono}>
|
||||
{cookies.TROLLCALL_NAME}
|
||||
</span>
|
||||
</p>
|
||||
<p className={globals.text}>
|
||||
CODE:{" "}
|
||||
<span
|
||||
className={`${globals.mono} ${styles.showOnHover}`}
|
||||
>
|
||||
{isClient && cookies.TROLLCALL_CODE}
|
||||
</span>
|
||||
</p>
|
||||
</details>
|
||||
<button
|
||||
className={globals.button}
|
||||
onClick={() => {
|
||||
deleteCookie("TROLLCALL_NAME", {
|
||||
path: "/"
|
||||
});
|
||||
deleteCookie("TROLLCALL_CODE", {
|
||||
path: "/"
|
||||
});
|
||||
router.push("/");
|
||||
}}
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Box properties={{ title: { text: "Sign in / join a clan" } }}>
|
||||
<p className={globals.text}>Join a clan on TrollCall.</p>
|
||||
<p className={globals.text}>
|
||||
Have you remembered your credientials? If not,{" "}
|
||||
<b>submit an issue</b> or <b>join the Discord</b> for help.
|
||||
</p>
|
||||
<div className={globals.verticalListTop}>
|
||||
<p className={globals.titleSmall}>Name</p>
|
||||
<div className={globals.horizontalListLeft}>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder="jim"
|
||||
ref={nameInput}
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={globals.verticalListTop}>
|
||||
<p className={globals.titleSmall}>Code</p>
|
||||
<div className={globals.horizontalListLeft}>
|
||||
<input
|
||||
type="password"
|
||||
name="code"
|
||||
placeholder="Tim Sweeney"
|
||||
ref={codeInput}
|
||||
className={`
|
||||
${form_globals.textLikeInput}
|
||||
${form_globals.textLikeInputTight}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className={globals.button}
|
||||
onClick={async () => {
|
||||
setCookie(
|
||||
"TROLLCALL_NAME",
|
||||
nameInput.current?.value.toLowerCase() ?? "",
|
||||
{
|
||||
path: "/",
|
||||
maxAge: 31540000
|
||||
}
|
||||
);
|
||||
setCookie(
|
||||
"TROLLCALL_CODE",
|
||||
codeInput.current?.value ?? "",
|
||||
{
|
||||
path: "/",
|
||||
maxAge: 31540000
|
||||
}
|
||||
);
|
||||
const status = await fetch("/api/test/signin");
|
||||
if (status.status === 200) router.push("/");
|
||||
else setStatusHolder(await status.text());
|
||||
}}
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
<span className={globals.text}>{statusHolder}</span>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -85,6 +85,8 @@ export default function Index({
|
|||
]
|
||||
}
|
||||
});
|
||||
|
||||
const [statusHolder, setStatusHolder] = useState("");
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
|
@ -176,7 +178,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0.9, 0.9, 0.8),
|
||||
new Color3(0.7, 0.7, 0.6),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -189,7 +192,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0.5, 0.5, 0.4),
|
||||
new Color3(0.1, 0.1, 0),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -202,6 +206,7 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0, 0, 0),
|
||||
new Color3(0, 0, 0),
|
||||
undefined,
|
||||
false
|
||||
])
|
||||
}
|
||||
|
@ -215,6 +220,7 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(1, 1, 1),
|
||||
new Color3(1, 1, 1),
|
||||
undefined,
|
||||
true
|
||||
])
|
||||
}
|
||||
|
@ -230,7 +236,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(1, 0, 0),
|
||||
new Color3(0.5, 0, 0),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -242,7 +249,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(1, 0.5, 0),
|
||||
new Color3(0.5, 0.25, 0),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -254,7 +262,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(1, 1, 0),
|
||||
new Color3(0.5, 0.5, 0),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -266,7 +275,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0.5, 1, 0),
|
||||
new Color3(0.25, 0.5, 0),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -278,7 +288,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0, 1, 0),
|
||||
new Color3(0, 0.5, 0),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -290,7 +301,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0, 1, 0.5),
|
||||
new Color3(0, 0.5, 0.25),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -302,7 +314,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0, 1, 1),
|
||||
new Color3(0, 0.5, 0.5),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -314,7 +327,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0, 0.5, 1),
|
||||
new Color3(0, 0.25, 0.5),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -326,7 +340,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0, 0, 1),
|
||||
new Color3(0, 0, 0.5),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -338,7 +353,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0.5, 0, 1),
|
||||
new Color3(0.25, 0, 0.5),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -350,7 +366,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(1, 0, 1),
|
||||
new Color3(0.5, 0, 0.5),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -362,7 +379,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(1, 0, 0.5),
|
||||
new Color3(0.5, 0, 0.25),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -377,7 +395,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0, 0.5, 0.6),
|
||||
new Color3(0, 0.1, 0.2),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -390,7 +409,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0.6, 0, 0),
|
||||
new Color3(0.2, 0, 0),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -403,7 +423,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0.5, 0, 0.6),
|
||||
new Color3(0.1, 0, 0.2),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -416,7 +437,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0.5, 0.5, 0.6),
|
||||
new Color3(0, 0, 0.1),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -429,7 +451,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(1, 1, 0),
|
||||
new Color3(0.5, 1, 0),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -442,7 +465,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(1, 0, 0),
|
||||
new Color3(1, 0.75, 0),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -459,6 +483,7 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0.6, 0, 1),
|
||||
new Color3(0, 0.5, 0.5),
|
||||
undefined,
|
||||
false
|
||||
])
|
||||
}
|
||||
|
@ -472,6 +497,7 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(1, 0.6, 0),
|
||||
new Color3(0.5, 1, 1),
|
||||
undefined,
|
||||
true
|
||||
])
|
||||
}
|
||||
|
@ -485,7 +511,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0, 1, 0),
|
||||
new Color3(1, 1, 1),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -498,7 +525,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(1, 0.5, 0.75),
|
||||
new Color3(0.5, 0.25, 0.375),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -511,7 +539,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(1, 0.25, 0.5),
|
||||
new Color3(0.5, 0.125, 0.25),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -524,7 +553,8 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0.5, 0.9, 1),
|
||||
new Color3(0.25, 0.45, 0.5),
|
||||
theme[2]
|
||||
undefined,
|
||||
undefined
|
||||
])
|
||||
}
|
||||
>
|
||||
|
@ -539,6 +569,7 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0.5, 1, 0.5),
|
||||
new Color3(1, 1, 1),
|
||||
undefined,
|
||||
true
|
||||
])
|
||||
}
|
||||
|
@ -551,6 +582,7 @@ export default function Index({
|
|||
setTheme([
|
||||
new Color3(0, 0.5, 0),
|
||||
new Color3(0, 0, 0),
|
||||
undefined,
|
||||
false
|
||||
])
|
||||
}
|
||||
|
@ -572,7 +604,12 @@ export default function Index({
|
|||
<button
|
||||
className={globals.button}
|
||||
onClick={() =>
|
||||
setTheme([theme[0], theme[1], !theme[2]])
|
||||
setTheme([
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
!theme[3]
|
||||
])
|
||||
}
|
||||
>
|
||||
Invert theme palette
|
||||
|
@ -647,6 +684,18 @@ export default function Index({
|
|||
globals.buttonLink applied to button
|
||||
</button>
|
||||
</Box>
|
||||
<Box properties={{ title: { text: "Auth test module" } }}>
|
||||
<button
|
||||
className={globals.button}
|
||||
onClick={async () => {
|
||||
const status = await fetch("/api/test/signin");
|
||||
setStatusHolder(await status.text());
|
||||
}}
|
||||
>
|
||||
Test auth
|
||||
</button>
|
||||
<span className={globals.text}>{statusHolder}</span>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
15
src/pages/troll/[clan]/[troll]/gallery/index.tsx
Normal file
15
src/pages/troll/[clan]/[troll]/gallery/index.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import Box from "@/components/Box/Box";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
|
||||
export default function Index({
|
||||
themerVars: [theme, setTheme]
|
||||
}: {
|
||||
themerVars: ThemerGetSet;
|
||||
}) {
|
||||
return (
|
||||
<Box properties={{ title: { text: "Work in progress" } }}>
|
||||
<span className={globals.text}>Sorry :[</span>
|
||||
</Box>
|
||||
);
|
||||
}
|
|
@ -3,33 +3,39 @@ import ClanCard from "@/components/cards/ClanCard/ClanCard";
|
|||
import TrollCard from "@/components/cards/TrollCard/TrollCard";
|
||||
import { TrollGET } from "@/lib/trollcall/api/troll";
|
||||
import { getSingleClan } from "@/lib/trollcall/clan";
|
||||
import { cutObject } from "@/lib/trollcall/utility/merge";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import { Color3 } from "@/types/assist/color";
|
||||
import { ThemerGetSet, WidthGetSet } from "@/types/generics";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
import { ClientTroll } from "@/types/troll";
|
||||
import { ProperNounCase } from "@/utility/language";
|
||||
import { parseQuirk } from "@/utility/quirk";
|
||||
import AuthContext from "@/utility/react/AuthContext";
|
||||
import Conditional from "@/utility/react/Conditional";
|
||||
import { GetServerSideProps, GetStaticPropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
export default function Index({
|
||||
themerVars: [theme, setTheme],
|
||||
widthVars: [width, setWidth],
|
||||
troll
|
||||
}: {
|
||||
themerVars: ThemerGetSet;
|
||||
widthVars: WidthGetSet;
|
||||
troll: ClientTroll;
|
||||
}) {
|
||||
const [fetchedTrolls, setFetchedTrolls] = useState<ClientTroll[] | null>(
|
||||
null
|
||||
);
|
||||
// Get user creds
|
||||
const userCredentials = useContext(AuthContext);
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
useEffect(() => setIsClient(true), []);
|
||||
|
||||
// Stack troll colors
|
||||
const trollColor = (troll.pageColor?.map(x => x / 255) ??
|
||||
troll.textColor?.map(x => x / 255) ??
|
||||
troll.falseSign?.color.color ??
|
||||
troll.trueSign?.color.color) as [number, number, number] | undefined;
|
||||
|
||||
// Merge policies
|
||||
const policies = {
|
||||
fanart: troll.policies?.fanart ?? troll.owner.policies.fanart,
|
||||
fanartOthers:
|
||||
|
@ -43,10 +49,10 @@ export default function Index({
|
|||
if (trollColor != null)
|
||||
setTheme([
|
||||
new Color3(...trollColor),
|
||||
new Color3(...trollColor).darken(50)
|
||||
new Color3(...trollColor).darken(50),
|
||||
1024
|
||||
]);
|
||||
}, []);
|
||||
setWidth(1024);
|
||||
return (
|
||||
<>
|
||||
<div className={styles.boxbox}>
|
||||
|
@ -56,20 +62,6 @@ export default function Index({
|
|||
link={false}
|
||||
small={false}
|
||||
/>
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text: "More..."
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
className={globals.linkButton}
|
||||
href={`/gallery/${troll.owner.name}/${troll.name[0]}/`}
|
||||
>
|
||||
Gallery
|
||||
</Link>
|
||||
</Box>
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
|
@ -211,19 +203,15 @@ export default function Index({
|
|||
format_quote
|
||||
</span>
|
||||
<span className={globals.text}>
|
||||
{parseQuirk(
|
||||
{troll.quirks != null
|
||||
? parseQuirk(
|
||||
quote,
|
||||
troll.quirks["default"]
|
||||
)}
|
||||
)
|
||||
: quote}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<Link
|
||||
className={globals.linkButton}
|
||||
href={`/quirk/${troll.owner.name}/${troll.name[0]}/`}
|
||||
>
|
||||
Quirk Tester
|
||||
</Link>
|
||||
</Box>
|
||||
</Conditional>
|
||||
</Box>
|
||||
|
@ -233,15 +221,7 @@ export default function Index({
|
|||
style={{ backgroundImage: `url("${troll.images[0]}")` }}
|
||||
></div>
|
||||
</div>
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text: "Owned by"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ClanCard clan={troll.owner} />
|
||||
</Box>
|
||||
<Conditional condition={troll.description != ""}>
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
|
@ -251,6 +231,65 @@ export default function Index({
|
|||
>
|
||||
<p className={globals.blockText}>{troll.description}</p>
|
||||
</Box>
|
||||
</Conditional>
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text: "More..."
|
||||
}
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
Some more stuff you can do with{" "}
|
||||
<b>{ProperNounCase(troll.name[0])}</b>.
|
||||
</p>
|
||||
<Link
|
||||
className={globals.linkButton}
|
||||
href={`/troll/${troll.owner.name}/${troll.name[0]}/gallery/`}
|
||||
>
|
||||
Gallery
|
||||
</Link>
|
||||
<Conditional condition={troll.quirks != null}>
|
||||
<Link
|
||||
className={globals.linkButton}
|
||||
href={`/troll/${troll.owner.name}/${troll.name[0]}/quirk/`}
|
||||
>
|
||||
Quirk Tester
|
||||
</Link>
|
||||
</Conditional>
|
||||
</Box>
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text: "Owned by"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ClanCard clan={troll.owner} />
|
||||
</Box>
|
||||
{isClient && userCredentials.TROLLCALL_NAME == troll.owner.name ? (
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text: "Admin"
|
||||
},
|
||||
theme: theme[1]
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
Well, it seems you can manage{" "}
|
||||
<b>{ProperNounCase(troll.name[0])}</b>! Have this menu.
|
||||
</p>
|
||||
<Link
|
||||
className={globals.linkButton}
|
||||
href={`/edit/troll/${troll.owner.name}/${troll.name[0]}/`}
|
||||
>
|
||||
Edit Troll
|
||||
</Link>
|
||||
</Box>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -274,7 +313,7 @@ export const getServerSideProps: GetServerSideProps<{
|
|||
};
|
||||
return {
|
||||
props: {
|
||||
troll
|
||||
troll: cutObject(troll)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
15
src/pages/troll/[clan]/[troll]/quirk/index.tsx
Normal file
15
src/pages/troll/[clan]/[troll]/quirk/index.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import Box from "@/components/Box/Box";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
|
||||
export default function Index({
|
||||
themerVars: [theme, setTheme]
|
||||
}: {
|
||||
themerVars: ThemerGetSet;
|
||||
}) {
|
||||
return (
|
||||
<Box properties={{ title: { text: "Work in progress" } }}>
|
||||
<span className={globals.text}>Sorry :[</span>
|
||||
</Box>
|
||||
);
|
||||
}
|
110
src/pages/troll/s/[clan]/index.tsx
Normal file
110
src/pages/troll/s/[clan]/index.tsx
Normal file
|
@ -0,0 +1,110 @@
|
|||
import Box from "@/components/Box/Box";
|
||||
import TrollCard from "@/components/cards/TrollCard/TrollCard";
|
||||
import TrollSkeleton from "@/components/cards/TrollCard/TrollSkeleton";
|
||||
import { ClanGET } from "@/lib/trollcall/api/clan";
|
||||
import { cutObject } from "@/lib/trollcall/utility/merge";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import "@/styles/index.module.css";
|
||||
import { Color3 } from "@/types/assist/color";
|
||||
import { ClientClan } from "@/types/clan";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
import { ClientTroll } from "@/types/troll";
|
||||
import { defaultTheme } from "@/utility/react/Themer";
|
||||
import { getCookies } from "cookies-next";
|
||||
import { GetServerSideProps, GetStaticPropsContext } from "next";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
getCookies();
|
||||
|
||||
export default function Index({
|
||||
themerVars: [theme, setTheme],
|
||||
clan
|
||||
}: {
|
||||
themerVars: ThemerGetSet;
|
||||
clan: ClientClan;
|
||||
}) {
|
||||
const [fetchedTrolls, setFetchedTrolls] = useState<ClientTroll[]>([]);
|
||||
const [trollPageNum, setTrollPageNum] = useState(0);
|
||||
const [noMore, setNoMore] = useState(false);
|
||||
async function getTroll(page?: number) {
|
||||
const res = await fetch(
|
||||
page
|
||||
? "/api/troll/" + clan.name + "/.../" + page
|
||||
: "/api/troll/" + clan.name + "/..."
|
||||
);
|
||||
const json = await res.json();
|
||||
setFetchedTrolls(fetchedTrolls.concat(json));
|
||||
setNoMore(json.length < 5);
|
||||
}
|
||||
useEffect(() => {
|
||||
getTroll(trollPageNum);
|
||||
setTheme(defaultTheme);
|
||||
const color = clan.color?.map(x => x / 255) as [number, number, number];
|
||||
if (color != null)
|
||||
setTheme([
|
||||
new Color3(...color),
|
||||
new Color3(...color).darken(50),
|
||||
768
|
||||
]);
|
||||
}, [trollPageNum]);
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text:
|
||||
"Characters of " + (clan.displayName ?? clan.name)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className={globals.text}>
|
||||
See a list of the characters of{" "}
|
||||
{clan.displayName ?? clan.name}.
|
||||
</span>
|
||||
</Box>
|
||||
<Box>
|
||||
{fetchedTrolls.length <= 0 ? (
|
||||
<>
|
||||
<TrollSkeleton />
|
||||
<TrollSkeleton />
|
||||
<TrollSkeleton />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{fetchedTrolls.map((troll: ClientTroll, idx) => (
|
||||
<TrollCard
|
||||
troll={troll}
|
||||
key={idx + "troll"}
|
||||
/>
|
||||
))}
|
||||
<button
|
||||
className={globals.button}
|
||||
onClick={() => {
|
||||
setTrollPageNum(trollPageNum + 1);
|
||||
}}
|
||||
disabled={noMore}
|
||||
>
|
||||
Load more
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<{
|
||||
clan: ClientClan;
|
||||
}> = async (context: GetStaticPropsContext) => {
|
||||
if (context.params?.clan == null) return { notFound: true };
|
||||
const clan = await ClanGET({ name: context.params.clan });
|
||||
if (clan == null)
|
||||
return {
|
||||
notFound: true
|
||||
};
|
||||
return {
|
||||
props: {
|
||||
clan: cutObject(clan)
|
||||
}
|
||||
};
|
||||
};
|
76
src/pages/troll/s/index.tsx
Normal file
76
src/pages/troll/s/index.tsx
Normal file
|
@ -0,0 +1,76 @@
|
|||
import Box from "@/components/Box/Box";
|
||||
import TrollCard from "@/components/cards/TrollCard/TrollCard";
|
||||
import TrollSkeleton from "@/components/cards/TrollCard/TrollSkeleton";
|
||||
import globals from "@/styles/global.module.css";
|
||||
import "@/styles/index.module.css";
|
||||
import { ThemerGetSet } from "@/types/generics";
|
||||
import { ClientTroll } from "@/types/troll";
|
||||
import { defaultTheme } from "@/utility/react/Themer";
|
||||
import { getCookies } from "cookies-next";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
getCookies();
|
||||
|
||||
export default function Index({
|
||||
themerVars: [theme, setTheme]
|
||||
}: {
|
||||
themerVars: ThemerGetSet;
|
||||
}) {
|
||||
const [fetchedTrolls, setFetchedTrolls] = useState<ClientTroll[]>([]);
|
||||
const [trollPageNum, setTrollPageNum] = useState(0);
|
||||
const [noMore, setNoMore] = useState(false);
|
||||
async function getTroll(page?: number) {
|
||||
const res = await fetch(
|
||||
page ? "/api/troll/.../" + page : "/api/troll/..."
|
||||
);
|
||||
const json = await res.json();
|
||||
setFetchedTrolls(fetchedTrolls.concat(json));
|
||||
setNoMore(json.length < 5);
|
||||
}
|
||||
useEffect(() => {
|
||||
getTroll(trollPageNum);
|
||||
setTheme(defaultTheme);
|
||||
}, [trollPageNum]);
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
properties={{
|
||||
title: {
|
||||
text: "Explore characters"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className={globals.text}>
|
||||
See a list of all the characters on TrollCall.
|
||||
</span>
|
||||
</Box>
|
||||
<Box>
|
||||
{fetchedTrolls.length <= 0 ? (
|
||||
<>
|
||||
<TrollSkeleton />
|
||||
<TrollSkeleton />
|
||||
<TrollSkeleton />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{fetchedTrolls.map((troll: ClientTroll, idx) => (
|
||||
<TrollCard
|
||||
troll={troll}
|
||||
key={idx + "troll"}
|
||||
/>
|
||||
))}
|
||||
<button
|
||||
className={globals.button}
|
||||
onClick={() => {
|
||||
setTrollPageNum(trollPageNum + 1);
|
||||
}}
|
||||
disabled={noMore}
|
||||
>
|
||||
Load more
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -22,10 +22,12 @@ main.App {
|
|||
color: var(--sec-fg);
|
||||
background-image: url("/assets/pattern/pattern.png");
|
||||
background-blend-mode: hard-light;
|
||||
--darken: linear-gradient(#0008, #0008);
|
||||
}
|
||||
|
||||
main.App.inverted {
|
||||
background-image: url("/assets/pattern/pattern_inv.png");
|
||||
--darken: linear-gradient(#fff8, #fff8);
|
||||
}
|
||||
|
||||
main.App div.mainContent {
|
||||
|
|
|
@ -31,12 +31,18 @@
|
|||
transition: box-shadow 0.125s;
|
||||
}
|
||||
|
||||
.linkButton:hover {
|
||||
.linkButton:hover:not(:disabled) {
|
||||
text-decoration: none;
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
}
|
||||
|
||||
.button {
|
||||
.linkButton:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.button,
|
||||
.buttonIcon {
|
||||
color: inherit;
|
||||
box-shadow: 0 0 2px currentColor;
|
||||
background-color: transparent;
|
||||
|
@ -52,10 +58,22 @@
|
|||
transition: box-shadow 0.125s;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
.button:hover:not(:disabled),
|
||||
.buttonIcon:hover:not(:disabled) {
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
}
|
||||
|
||||
.button:disabled,
|
||||
.buttonIcon:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.buttonIcon {
|
||||
font-family: "Material Symbols Outlined";
|
||||
font-variation-settings: "FILL" 1, "opsz" 24, "GRAD" 0, "wght" 400;
|
||||
}
|
||||
|
||||
.buttonLink {
|
||||
color: inherit;
|
||||
background-color: transparent;
|
||||
|
@ -107,6 +125,14 @@
|
|||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.blockTextKeepTabs {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-family: "Space Grotesk";
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
|
@ -124,6 +150,7 @@
|
|||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
vertical-align: middle;
|
||||
font-family: "Material Symbols Outlined";
|
||||
font-variation-settings: "FILL" 1, "opsz" 24, "GRAD" 0, "wght" 400;
|
||||
user-select: none;
|
||||
|
@ -162,14 +189,15 @@
|
|||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0 8px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.horizontalListLeft {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
gap: 0 8px;
|
||||
gap: 8px;
|
||||
}
|
||||
.verticalListTop {
|
||||
display: flex;
|
||||
|
@ -277,3 +305,7 @@
|
|||
width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.forceLTR {
|
||||
direction: ltr;
|
||||
}
|
||||
|
|
152
src/styles/global_form.module.css
Normal file
152
src/styles/global_form.module.css
Normal file
|
@ -0,0 +1,152 @@
|
|||
/* Layout changes */
|
||||
|
||||
.FlexNormalizer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.verticalListCrunch {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.layoutAppearable {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
margin-top: -8px;
|
||||
transition: all 0.125s;
|
||||
}
|
||||
|
||||
.layoutAppearable.appear {
|
||||
opacity: 1;
|
||||
height: initial;
|
||||
overflow: visible;
|
||||
margin-top: 0;
|
||||
transition: all 0.125s;
|
||||
}
|
||||
|
||||
.layoutButtonDisabled {
|
||||
opacity: 0.25;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.horizlist {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.vertilist {
|
||||
display: flex;
|
||||
/* flex-wrap: wrap; */
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 16px !important;
|
||||
/* padding: 8px !important; */
|
||||
}
|
||||
|
||||
/* Text stuff */
|
||||
|
||||
.render_info {
|
||||
color: #8cf;
|
||||
}
|
||||
|
||||
.render_warning {
|
||||
color: #ff8;
|
||||
}
|
||||
|
||||
.render_error {
|
||||
color: #f88;
|
||||
}
|
||||
|
||||
.iconSmall {
|
||||
display: inline-block;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
font-family: "Material Symbols Outlined";
|
||||
font-variation-settings: "FILL" 1, "opsz" 24, "GRAD" 0, "wght" 400;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.iconTextForm span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
.textLikeInput {
|
||||
width: 25ch;
|
||||
color: inherit;
|
||||
box-shadow: inset 0 0 2px currentColor;
|
||||
background-color: transparent;
|
||||
border: 1px solid currentColor;
|
||||
|
||||
padding: 4px 8px;
|
||||
border-radius: 2px;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-family: "Space Grotesk";
|
||||
cursor: text;
|
||||
|
||||
transition: box-shadow 0.125s;
|
||||
}
|
||||
|
||||
.textLikeInput:hover,
|
||||
.textLikeInput:focus,
|
||||
.textLikeInput:active {
|
||||
box-shadow: inset 0 0 8px currentColor;
|
||||
}
|
||||
|
||||
.textLikeInputTight {
|
||||
width: 20ch;
|
||||
}
|
||||
|
||||
.textLikeInputSmall {
|
||||
width: 5ch;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.textLikeInputInvisible {
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.textLikeInputInvisible:hover,
|
||||
.textLikeInputInvisible:focus,
|
||||
.textLikeInputInvisible:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.textLikeInputMultiline {
|
||||
padding: 8px;
|
||||
height: 10em;
|
||||
max-height: 30em;
|
||||
resize: vertical;
|
||||
width: calc(100% - 18px);
|
||||
}
|
||||
|
||||
.textLikeInputMono {
|
||||
font-family: "Space Mono";
|
||||
}
|
||||
|
||||
.selectInput {
|
||||
cursor: default;
|
||||
}
|
|
@ -1,67 +1,5 @@
|
|||
@import url(./fonts.css);
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
line-height: 20px;
|
||||
font-family: "Flow Circular";
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.titleSmall {
|
||||
font-size: 20px;
|
||||
line-height: 16px;
|
||||
font-family: "Flow Circular";
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
font-family: "Flow Circular";
|
||||
}
|
||||
|
||||
.blockText {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
font-family: "Flow Circular";
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
font-family: "Flow Circular";
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-family: "Flow Circular";
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
font-family: "Material Symbols Outlined";
|
||||
font-variation-settings: "FILL" 1, "opsz" 24, "GRAD" 0, "wght" 400;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.iconSmall {
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
font-family: "Material Symbols Outlined";
|
||||
font-variation-settings: "FILL" 1, "opsz" 24, "GRAD" 0, "wght" 400;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.iconlike {
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
font-family: "Flow Circular";
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
@ -75,6 +13,29 @@
|
|||
text-shadow: 0 0 8px currentColor;
|
||||
}
|
||||
|
||||
.linkButton {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
box-shadow: 0 0 2px currentColor;
|
||||
background-color: transparent;
|
||||
border: 1px solid currentColor;
|
||||
|
||||
padding: 4px 8px;
|
||||
border-radius: 2px;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-family: "Space Grotesk";
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
|
||||
transition: box-shadow 0.125s;
|
||||
}
|
||||
|
||||
.linkButton:hover {
|
||||
text-decoration: none;
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
}
|
||||
|
||||
.button {
|
||||
color: inherit;
|
||||
box-shadow: 0 0 2px currentColor;
|
||||
|
@ -85,7 +46,7 @@
|
|||
border-radius: 2px;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-family: "Flow Circular";
|
||||
font-family: "Space Grotesk";
|
||||
cursor: pointer;
|
||||
|
||||
transition: box-shadow 0.125s;
|
||||
|
@ -102,7 +63,7 @@
|
|||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-family: "Flow Circular";
|
||||
font-family: "Space Grotesk";
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: inline;
|
||||
|
@ -118,20 +79,118 @@
|
|||
text-shadow: 0 0 8px currentColor;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-family: "TrollCall Display";
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.titleSmall {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
font-family: "TrollCall Display";
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-family: "Space Grotesk";
|
||||
}
|
||||
|
||||
.blockText {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-family: "Space Grotesk";
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
font-family: "Space Grotesk";
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-family: "Space Mono";
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
font-family: "Material Symbols Outlined";
|
||||
font-variation-settings: "FILL" 1, "opsz" 24, "GRAD" 0, "wght" 400;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.iconSmall {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
height: 16px;
|
||||
font-family: "Material Symbols Outlined";
|
||||
font-variation-settings: "FILL" 1, "opsz" 24, "GRAD" 0, "wght" 400;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.iconlike {
|
||||
display: inline-block;
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
padding-bottom: 2px;
|
||||
vertical-align: middle;
|
||||
font-family: "Space Mono";
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.boxLike {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.buttonRow,
|
||||
.horizontalList {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0 8px;
|
||||
}
|
||||
|
||||
.buttonRow {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.horizontalListLeft {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
gap: 0 8px;
|
||||
}
|
||||
.verticalListTop {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
gap: 8px 0;
|
||||
}
|
||||
.flexRow {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
gap: 0 8px;
|
||||
}
|
||||
|
||||
.iconText {
|
||||
|
@ -141,3 +200,85 @@
|
|||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.iconTextTop {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.buttonLeft {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.buttonRight {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.sep {
|
||||
width: 100%;
|
||||
background-color: currentColor;
|
||||
color: currentColor;
|
||||
padding: 0;
|
||||
margin: 8px 0;
|
||||
border: none;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.sepHeader {
|
||||
width: 100%;
|
||||
background-color: currentColor;
|
||||
color: currentColor;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.invisep {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
color: transparent;
|
||||
padding: 0;
|
||||
margin: 2px 0;
|
||||
border: none;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.invisepHeader {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
color: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.signage {
|
||||
filter: drop-shadow(1px 1px 0 currentcolor)
|
||||
drop-shadow(-1px -1px 0 currentcolor)
|
||||
drop-shadow(1px -1px 0 currentcolor)
|
||||
drop-shadow(-1px 1px 0 currentcolor);
|
||||
}
|
||||
|
||||
.headerPG {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: -128px;
|
||||
height: 0;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,14 @@ export const SubmitClanSchema = yup
|
|||
.min(3)
|
||||
.max(50)
|
||||
.lowercase(),
|
||||
displayName: yup.string().notRequired().min(3).max(100),
|
||||
displayName: yup
|
||||
.string()
|
||||
.notRequired()
|
||||
.min(3)
|
||||
.max(100)
|
||||
.transform(v => {
|
||||
return v.length <= 0 ? undefined : v;
|
||||
}),
|
||||
members: yup
|
||||
.array()
|
||||
.of(
|
||||
|
|
|
@ -11,10 +11,10 @@ export const SubmitQuirkHolderSchema = yup
|
|||
])
|
||||
.required()
|
||||
)
|
||||
.required()
|
||||
.test("has-default", 'Needs "default" Quirk Mode', v =>
|
||||
v.some(([k, v]) => k === "default" || k === "Default")
|
||||
);
|
||||
.test("has-default", 'Needs "default" Quirk Mode', v => {
|
||||
if (v == undefined) return true;
|
||||
return v.some(([k, v]) => k === "default" || k === "Default");
|
||||
});
|
||||
|
||||
export type SubmitQuirkHolder = yup.InferType<typeof SubmitQuirkHolderSchema>;
|
||||
|
||||
|
|
|
@ -12,14 +12,14 @@ export const SubmitTrollSchema = yup
|
|||
.string()
|
||||
.required()
|
||||
.matches(/^[A-z]+$/, "Letters only")
|
||||
.min(1)
|
||||
.min(3)
|
||||
.max(24)
|
||||
.lowercase(),
|
||||
yup
|
||||
.string()
|
||||
.required()
|
||||
.matches(/^[A-z]+$/, "Letters only")
|
||||
.min(1)
|
||||
.min(3)
|
||||
.max(24)
|
||||
.lowercase()
|
||||
])
|
||||
|
@ -71,29 +71,56 @@ export const SubmitTrollSchema = yup
|
|||
.required()
|
||||
)
|
||||
.required()
|
||||
.min(1),
|
||||
.min(1)
|
||||
.max(20),
|
||||
gender: yup
|
||||
.string()
|
||||
.required()
|
||||
.matches(/^[A-z- ]+$/, "Letters only")
|
||||
.min(3)
|
||||
.max(30),
|
||||
.max(30)
|
||||
.transform(v => {
|
||||
return v.length <= 0 ? undefined : v;
|
||||
}),
|
||||
|
||||
// Personal
|
||||
preferences: yup.object({
|
||||
preferences: yup
|
||||
.object({
|
||||
love: yup
|
||||
.array()
|
||||
.of(yup.string().required().min(5).max(500))
|
||||
.max(10),
|
||||
.min(1)
|
||||
.max(10)
|
||||
.transform(v => {
|
||||
return v.length <= 0 ? undefined : v;
|
||||
}),
|
||||
hate: yup
|
||||
.array()
|
||||
.of(yup.string().required().min(5).max(500))
|
||||
.min(1)
|
||||
.max(10)
|
||||
.transform(v => {
|
||||
return v.length <= 0 ? undefined : v;
|
||||
})
|
||||
})
|
||||
.transform(v => {
|
||||
return v.love == null && v.hate == null ? undefined : v;
|
||||
}),
|
||||
facts: yup
|
||||
.array()
|
||||
.of(yup.string().required().min(5).max(500))
|
||||
.min(1)
|
||||
.max(10)
|
||||
.transform(v => {
|
||||
return v.length <= 0 ? null : v;
|
||||
}),
|
||||
facts: yup.array().of(yup.string().required().min(5).max(500)).max(10),
|
||||
|
||||
// Hiveswap identity
|
||||
trueSign: yup.string().required().oneOf(TrueSignKeys),
|
||||
trueSign: yup
|
||||
.string()
|
||||
.nullable()
|
||||
.transform(v => {
|
||||
return v === "" ? null : v;
|
||||
})
|
||||
.oneOf(TrueSignKeys),
|
||||
falseSign: yup
|
||||
.string()
|
||||
.nullable()
|
||||
|
@ -101,10 +128,16 @@ export const SubmitTrollSchema = yup
|
|||
return v === "" ? null : v;
|
||||
})
|
||||
.oneOf(TrueSignKeys),
|
||||
class: yup.string().oneOf(ClassKeys),
|
||||
class: yup
|
||||
.string()
|
||||
.nullable()
|
||||
.transform(v => {
|
||||
return v === "" ? null : v;
|
||||
})
|
||||
.oneOf(ClassKeys),
|
||||
|
||||
// Trollian
|
||||
username: yup.string().max(100),
|
||||
username: yup.string().max(50),
|
||||
textColor: yup
|
||||
.tuple([
|
||||
yup.number().min(0).max(255).required(),
|
||||
|
@ -119,26 +152,54 @@ export const SubmitTrollSchema = yup
|
|||
yup.number().min(0).max(255).required()
|
||||
])
|
||||
.notRequired(), // colors the page.
|
||||
quirks: SubmitQuirkHolderSchema.required(), // DO NOT HANDLE RIGHT NOW.
|
||||
quotes: yup.array().of(yup.string().max(1000).required()).max(20),
|
||||
quirks: SubmitQuirkHolderSchema.notRequired(), // DO NOT HANDLE RIGHT NOW.
|
||||
quotes: yup
|
||||
.array()
|
||||
.of(yup.string().max(1000).required())
|
||||
.min(1)
|
||||
.max(20)
|
||||
.transform(v => {
|
||||
return v.length <= 0 ? undefined : v;
|
||||
}),
|
||||
|
||||
// Physical stuff
|
||||
species: yup
|
||||
.string()
|
||||
.notRequired()
|
||||
.matches(/^([A-z-]+)|()$/, "Letters only")
|
||||
.max(50), // "Troll-*" if defined. Otherwise, just "Troll".
|
||||
height: yup.number().required().positive(), // Inches
|
||||
age: yup.number().required().positive(), // Sweeps
|
||||
images: yup.array().of(yup.string().required().url()).required(),
|
||||
.matches(/^([A-z- ]+)|()$/, "Letters only")
|
||||
.max(50)
|
||||
.transform(v => {
|
||||
return v.length <= 0 ? undefined : v;
|
||||
}),
|
||||
height: yup.number().required().positive().max(1024), // Inches
|
||||
age: yup.number().required().positive().max(1024), // Sweeps
|
||||
images: yup
|
||||
.array()
|
||||
.of(yup.string().required().url())
|
||||
.required()
|
||||
.min(1)
|
||||
.max(20)
|
||||
.transform(v => {
|
||||
return v.length <= 0 ? undefined : v;
|
||||
}),
|
||||
// Meta stuff
|
||||
policies: yup
|
||||
.object({
|
||||
fanart: PolicySchema.notRequired(),
|
||||
fanartOthers: PolicySchema.notRequired(),
|
||||
kinning: PolicySchema.notRequired(),
|
||||
shipping: PolicySchema.notRequired(),
|
||||
fanfiction: PolicySchema.notRequired()
|
||||
fanart: PolicySchema.notRequired().transform(v => {
|
||||
return v === "" ? undefined : v;
|
||||
}),
|
||||
fanartOthers: PolicySchema.notRequired().transform(v => {
|
||||
return v === "" ? undefined : v;
|
||||
}),
|
||||
kinning: PolicySchema.notRequired().transform(v => {
|
||||
return v === "" ? undefined : v;
|
||||
}),
|
||||
shipping: PolicySchema.notRequired().transform(v => {
|
||||
return v === "" ? undefined : v;
|
||||
}),
|
||||
fanfiction: PolicySchema.notRequired().transform(v => {
|
||||
return v === "" ? undefined : v;
|
||||
})
|
||||
})
|
||||
.notRequired()
|
||||
})
|
||||
|
@ -202,15 +263,14 @@ export const PartialTrollSchema = yup
|
|||
gender: yup
|
||||
.string()
|
||||
.matches(/^[A-z-_]+$/, "Letters only")
|
||||
.min(3)
|
||||
.max(30),
|
||||
|
||||
// Personal
|
||||
preferences: yup.object({
|
||||
love: yup.array().of(yup.string().min(5).max(500)).max(10),
|
||||
hate: yup.array().of(yup.string().min(5).max(500)).max(10)
|
||||
love: yup.array().of(yup.string().min(5).max(500)).min(1).max(10),
|
||||
hate: yup.array().of(yup.string().min(5).max(500)).min(1).max(10)
|
||||
}),
|
||||
facts: yup.array().of(yup.string().min(5).max(500)).max(10),
|
||||
facts: yup.array().of(yup.string().min(5).max(500)).min(1).max(10),
|
||||
|
||||
// Hiveswap identity
|
||||
trueSign: yup.string().oneOf(TrueSignKeys),
|
||||
|
|
|
@ -6,9 +6,8 @@ export type AnyObjectAnd<T> = T & { [key: string]: any };
|
|||
|
||||
// Color
|
||||
|
||||
export type ThemerGetSet = [
|
||||
[Color3, Color3, boolean?],
|
||||
(x: [Color3, Color3, boolean?]) => void
|
||||
];
|
||||
export type ThemeGet = [Color3, Color3, number?, boolean?];
|
||||
export type ThemeGetOpt = [Color3?, Color3?, number?, boolean?];
|
||||
export type ThemeSet = (x: ThemeGetOpt) => void;
|
||||
|
||||
export type WidthGetSet = [number, (x: number) => void];
|
||||
export type ThemerGetSet = [ThemeGet, ThemeSet];
|
||||
|
|
|
@ -8,7 +8,7 @@ export function PronounGrouper(
|
|||
amount?: number
|
||||
) {
|
||||
if (pronouns.length > 1)
|
||||
return pronouns.map((pronounSet) => pronounSet[0]).join("/");
|
||||
return pronouns.map(pronounSet => pronounSet[0]).join("/");
|
||||
else return pronouns[0].slice(0, amount ?? 2).join(sep ?? "/");
|
||||
}
|
||||
|
||||
|
@ -17,11 +17,18 @@ export function HeightConverter(inches: number) {
|
|||
var meters = Math.floor((inches / 39.37) * 100) / 100 + "m";
|
||||
return feetandinches + " (" + meters + ")";
|
||||
}
|
||||
export function HeightConverterImperial(inches: number) {
|
||||
return Math.floor(inches / 12) + "'" + (inches % 12) + '"';
|
||||
}
|
||||
|
||||
export function HeightConverterMetric(inches: number) {
|
||||
return Math.floor((inches / 39.37) * 100) / 100 + "m";
|
||||
}
|
||||
|
||||
export function AgeConverter(age: number, years?: boolean) {
|
||||
return years
|
||||
? `${Math.round((age / 2.1667) * 10) / 10} sweeps`
|
||||
: `${Math.round(age * 2.1667 * 10) / 10} years`;
|
||||
if (years) return `${Math.round((age / 2.1667) * 10) / 10} sweeps`;
|
||||
else if (years == null) return `${Math.round(age)} years`;
|
||||
else return `${Math.round(age * 2.1667 * 10) / 10} years`;
|
||||
}
|
||||
|
||||
export function Pluralize(stringe: string) {
|
||||
|
@ -39,7 +46,7 @@ export function ArraySample(array: any[]) {
|
|||
export function ProperNounCase(string: string) {
|
||||
return string
|
||||
.split(" ")
|
||||
.map((x) => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase())
|
||||
.map(x => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase())
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
|
@ -47,7 +54,9 @@ export function PesterchumNameFormatter(string: string) {
|
|||
return (
|
||||
string +
|
||||
" [" +
|
||||
string.replace(/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/, "$2$4").toUpperCase() +
|
||||
string
|
||||
.replace(/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/, "$2$4")
|
||||
.toUpperCase() +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Color3 } from "@/types/assist/color";
|
||||
import { ThemeGet } from "@/types/generics";
|
||||
import { createContext } from "react";
|
||||
|
||||
export default function Themer({
|
||||
|
@ -37,11 +38,14 @@ export default function Themer({
|
|||
|
||||
export const ThemeModeContext = createContext(false as boolean | undefined);
|
||||
|
||||
export const defaultTheme: [Color3, Color3] = [
|
||||
export const defaultTheme: ThemeGet = [
|
||||
new Color3(0.9, 0.9, 0.8),
|
||||
new Color3(0.7, 0.7, 0.6)
|
||||
new Color3(0.7, 0.7, 0.6),
|
||||
768
|
||||
];
|
||||
export const hiveswapTheme: [Color3, Color3] = [
|
||||
export const hiveswapTheme: ThemeGet = [
|
||||
new Color3(0.5, 0, 1),
|
||||
new Color3(0.25, 0, 0.5)
|
||||
new Color3(0.25, 0, 0.5),
|
||||
768,
|
||||
undefined
|
||||
];
|
||||
|
|
Loading…
Reference in a new issue