Optimize for Vercel, do some changes, entire site

This commit is contained in:
MeowcaTheoRange 2023-09-24 19:32:20 -05:00
parent d72cd85229
commit f06256d97e
74 changed files with 5243 additions and 585 deletions

877
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -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}>

View file

@ -23,6 +23,10 @@
max-width: 100%;
}
.Box.ltr {
direction: ltr;
}
.Box .innerContent {
padding: 6px 4px;
display: inline-flex;

View file

@ -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}>

View file

@ -1,12 +1,13 @@
import { Color3 } from "@/types/assist/color";
export type BoxConfig = {
title?: {
text: string;
link?: string;
small?: boolean;
};
theme?: Color3;
nfw?: boolean;
class?: string;
title?: {
text: string;
link?: string;
small?: boolean;
};
theme?: Color3;
nfw?: boolean;
ltr?: boolean;
class?: string;
};

View file

@ -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 {

View file

@ -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>

View file

@ -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; */
}

View file

@ -61,11 +61,13 @@ export default function ClanCard({
{clan.displayName ?? clan.name}
</ConditionalParent>
</p>
<div className={globals.horizontalListLeft}>
{clan.flairs.map(flair => (
<FlairCard flair={flair} />
))}
</div>
<Conditional condition={clan.flairs != null}>
<div className={globals.horizontalListLeft}>
{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>
<p className={globals.iconText}>
<span className={globals.iconSmall}>description</span>
<span className={globals.text}>{clan.description}</span>
</p>
<Conditional condition={clan.description.length > 0}>
<p className={globals.iconText}>
<span className={globals.iconSmall}>
description
</span>
<span className={globals.text}>
{clan.description}
</span>
</p>
</Conditional>
</div>
</div>
</Box>

View file

@ -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>

View file

@ -1,3 +1,7 @@
.FlairLink {
text-decoration: none;
}
.FlairCard {
background-color: var(--pri-bg);
color: var(--pri-fg);

View file

@ -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,12 +22,25 @@ export default function FlairCard({ flair }: { flair: ClientFlair }) {
}
) as React.CSSProperties;
return (
<div
className={styles.FlairCard}
style={style}
title={flair.alt}
<ConditionalParent
condition={flair.link != null}
parent={children => (
<Link
className={styles.FlairLink}
href={flair.link ?? ""}
target="_blank"
>
{children}
</Link>
)}
>
{flair.name}
</div>
<div
className={styles.FlairCard}
style={style}
title={flair.alt}
>
{flair.name}
</div>
</ConditionalParent>
);
}

View file

@ -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));
}

View file

@ -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>

View file

@ -82,46 +82,69 @@ export default function TrollCard({
<p className={globals.text}>
({troll.pronunciation.join(" ")})
</p>
<p className={globals.text}>
Also known as <b>{troll.username}</b> online.
</p>
<div className={globals.horizontalListLeft}>
{troll.flairs.map(flair => (
<FlairCard flair={flair} />
))}
</div>
<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 => (
<FlairCard flair={flair} />
))}
</div>
</Conditional>
<hr className={globals.invisep} />
<p className={globals.horizontalList}>
<Conditional condition={troll.class != null}>
<span className={globals.text}>
{troll.class?.name} of{" "}
{troll.trueSign?.aspect.name}
</span>
<span className={globals.text}>-</span>
</Conditional>
<span className={globals.text}>{troll.gender}</span>
<span className={globals.text}>-</span>
<span className={globals.text}>
{PronounGrouper(troll.pronouns)}
</span>
</p>
<Conditional
condition={
troll.class != null &&
troll.gender != null &&
troll.species != null
}
>
<p className={globals.horizontalList}>
<Conditional condition={troll.class != null}>
<span className={globals.text}>
{troll.class?.name} of{" "}
{troll.trueSign?.aspect.name}
</span>
<span className={globals.text}>-</span>
</Conditional>
<Conditional condition={troll.gender != null}>
<span className={globals.text}>
{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}
</span>
</Conditional>
<span className={globals.text}>-</span>
<span className={globals.text}>
{PronounGrouper(troll.pronouns)}
</span>
</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(
randomQuote,
troll.quirks["default"]
)}
{troll.quirks != null
? parseQuirk(
randomQuote,
troll.quirks["default"]
)
: randomQuote}
</span>
</p>
</Conditional>

View file

@ -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 &lt; 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 &gt;= 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>

View 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>
);
}

View 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
}

View 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>
);
}

File diff suppressed because it is too large Load diff

View 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>
</>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View file

@ -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");

View file

@ -17,11 +17,12 @@ export async function ClanGET(
: await getSingleClan(query);
if (clan == null) return null;
const serverClan = await ServerClanToClientClan({ ...clan });
serverClan.flairs = cutArray(
await getManyFlairs(
{ _id: { $in: clan.flairs } },
ServerFlairToClientFlair
)
);
if (clan.flairs != null)
serverClan.flairs = cutArray(
await getManyFlairs(
{ _id: { $in: clan.flairs } },
ServerFlairToClientFlair
)
);
return serverClan as ClientClan;
}

View file

@ -30,12 +30,13 @@ export async function TrollGET(
});
if (troll == null) return null;
const serverTroll = await ServerTrollToClientTroll(troll);
serverTroll.flairs = cutArray(
await getManyFlairs(
{ _id: { $in: troll.flairs } },
ServerFlairToClientFlair
)
);
if (troll.flairs != null)
serverTroll.flairs = cutArray(
await getManyFlairs(
{ _id: { $in: troll.flairs } },
ServerFlairToClientFlair
)
);
// we know this is not null, as we passed in our own clan
serverTroll.owner = (await ClanGET(null, clan)) as ClientClan;
return serverTroll as ClientTroll;

View file

@ -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"> {

View file

@ -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"> {

View file

@ -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>

View 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 />
);
}

View 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 />
);
}

View file

@ -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,

View file

@ -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
})

View file

@ -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
})

View 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;
// }
// */

View 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();
}

View file

@ -21,12 +21,13 @@ export default async function handler(
thisTroll.owner = (await ClanGET({
_id: troll.owner
})) as ClientClan;
thisTroll.flairs = cutArray(
await getManyFlairs(
{ _id: { $in: troll.flairs } },
ServerFlairToClientFlair
)
);
if (troll.flairs != null)
thisTroll.flairs = cutArray(
await getManyFlairs(
{ _id: { $in: troll.flairs } },
ServerFlairToClientFlair
)
);
return thisTroll;
},

View file

@ -27,12 +27,13 @@ export default async function handler(
async (troll: any) => {
const thisTroll = await ServerTrollToClientTroll(troll);
thisTroll.owner = clientClan;
thisTroll.flairs = cutArray(
await getManyFlairs(
{ _id: { $in: troll.flairs } },
ServerFlairToClientFlair
)
);
if (troll.flairs != null)
thisTroll.flairs = cutArray(
await getManyFlairs(
{ _id: { $in: troll.flairs } },
ServerFlairToClientFlair
)
);
return thisTroll;
},

View file

@ -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({

View file

@ -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
);
async function getTroll(page?: number) {
const res = await fetch(
page ? "/api/troll/.../" + page : "/api/troll/..."
);
const json = await res.json();
setFetchedTrolls(json);
}
useEffect(() => {
async function getTroll() {
const res = await fetch("/api/troll/" + clan.name + "/...");
const json = await res.json();
setFetchedTrolls(json);
}
getTroll();
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)
}
};
};

View 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>
</>
);
}

View 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)
}
};
};

View 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)
}
};
};

View file

@ -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}>

View file

@ -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
};
};

View file

@ -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}

View file

@ -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
};
};

View file

@ -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}

View file

@ -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>

View file

@ -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
};
};

View file

@ -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}

View file

@ -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
};
};

View file

@ -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}

View file

@ -21,23 +21,25 @@ export default function Index({
}: {
themerVars: ThemerGetSet;
}) {
const [fetchedTrolls, setFetchedTrolls] = useState<ClientTroll[] | null>(
null
);
const [fetchedClans, setFetchedClans] = useState<ClientClan[] | 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 json = await res.json();
setFetchedTrolls(json);
}
async function getClan(page?: number) {
const res = await fetch(
page ? "/api/clan/.../" + page : "/api/clan/..."
);
const json = await res.json();
setFetchedClans(json);
}
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/...");
const json = await res.json();
setFetchedClans(json);
}
getClan();
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
}
}}

View 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
View 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>
</>
);
}

View file

@ -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>
</>
);
}

View 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>
);
}

View file

@ -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(
quote,
troll.quirks["default"]
)}
{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,6 +221,43 @@ export default function Index({
style={{ backgroundImage: `url("${troll.images[0]}")` }}
></div>
</div>
<Conditional condition={troll.description != ""}>
<Box
properties={{
title: {
text: "Description"
}
}}
>
<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: {
@ -242,15 +267,29 @@ export default function Index({
>
<ClanCard clan={troll.owner} />
</Box>
<Box
properties={{
title: {
text: "Description"
}
}}
>
<p className={globals.blockText}>{troll.description}</p>
</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)
}
};
};

View 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>
);
}

View 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)
}
};
};

View 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>
</>
);
}

View file

@ -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 {

View file

@ -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;
}

View 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;
}

View file

@ -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;
}

View file

@ -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(

View file

@ -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>;

View file

@ -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({
love: yup
.array()
.of(yup.string().required().min(5).max(500))
.max(10),
hate: yup
.array()
.of(yup.string().required().min(5).max(500))
.max(10)
}),
facts: yup.array().of(yup.string().required().min(5).max(500)).max(10),
preferences: yup
.object({
love: yup
.array()
.of(yup.string().required().min(5).max(500))
.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;
}),
// 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),

View file

@ -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];

View file

@ -1,53 +1,62 @@
export function AdaptivePossessive(owner: string, possession: string) {
return owner + (owner.endsWith("s") ? "'" : "'s") + " " + possession;
return owner + (owner.endsWith("s") ? "'" : "'s") + " " + possession;
}
export function PronounGrouper(
pronouns: [string, string, string][],
sep?: string,
amount?: number
pronouns: [string, string, string][],
sep?: string,
amount?: number
) {
if (pronouns.length > 1)
return pronouns.map((pronounSet) => pronounSet[0]).join("/");
else return pronouns[0].slice(0, amount ?? 2).join(sep ?? "/");
if (pronouns.length > 1)
return pronouns.map(pronounSet => pronounSet[0]).join("/");
else return pronouns[0].slice(0, amount ?? 2).join(sep ?? "/");
}
export function HeightConverter(inches: number) {
var feetandinches = Math.floor(inches / 12) + "'" + (inches % 12) + '"';
var meters = Math.floor((inches / 39.37) * 100) / 100 + "m";
return feetandinches + " (" + meters + ")";
var feetandinches = Math.floor(inches / 12) + "'" + (inches % 12) + '"';
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) {
if (stringe.match(/(?:s|ch|sh|x|z|[^aeiou]o)$/)) return stringe + "es";
else if (stringe.match(/[aeiou](?:y|o)$/)) return stringe + "s";
else if (stringe.match(/[^aeiou]y$/)) return stringe.slice(0, -1) + "ies";
else if (stringe.match(/(?:f|fe)$/)) return stringe.slice(0, -1) + "ves";
else return stringe + "s";
if (stringe.match(/(?:s|ch|sh|x|z|[^aeiou]o)$/)) return stringe + "es";
else if (stringe.match(/[aeiou](?:y|o)$/)) return stringe + "s";
else if (stringe.match(/[^aeiou]y$/)) return stringe.slice(0, -1) + "ies";
else if (stringe.match(/(?:f|fe)$/)) return stringe.slice(0, -1) + "ves";
else return stringe + "s";
}
export function ArraySample(array: any[]) {
return array[Math.floor(Math.random() * array.length)];
return array[Math.floor(Math.random() * array.length)];
}
export function ProperNounCase(string: string) {
return string
.split(" ")
.map((x) => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase())
.join(" ");
return string
.split(" ")
.map(x => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase())
.join(" ");
}
export function PesterchumNameFormatter(string: string) {
return (
string +
" [" +
string.replace(/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/, "$2$4").toUpperCase() +
"]"
);
return (
string +
" [" +
string
.replace(/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/, "$2$4")
.toUpperCase() +
"]"
);
}

View file

@ -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
];