Merge pull request 'Update prod' (#1) from sitetest into master
Reviewed-on: https://git.calones.xyz/Rowblox/rowblox/pulls/1
This commit is contained in:
commit
dd31b5bafc
|
|
@ -6,3 +6,4 @@ node_modules
|
|||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
dev.sh
|
||||
22
README.md
22
README.md
|
|
@ -1,8 +1,20 @@
|
|||
# rowblox-sitetest
|
||||
# Rowblox
|
||||
This is the main repository for Rowblox development. **Please, only make commits to the sitetest branch.** They will be pulled into the main branch later.
|
||||
|
||||
This repo is for testing development, Major overhauls may happen often.
|
||||
## Roadmap
|
||||
- [x] Landing page
|
||||
- [x] Home page
|
||||
- [x] Navbar
|
||||
- [x] Footer
|
||||
|
||||
This is the development repository of `rowblox`.
|
||||
**PLEASE** prototype ideas here before committing your ideas to the production repository.
|
||||
Some1 please add all the features i am not going to remember everything
|
||||
|
||||
Hai
|
||||
## Contributors
|
||||
- row
|
||||
- riot
|
||||
- Dudebloke
|
||||
- calones (i-have-an-issue)
|
||||
- unexp
|
||||
- salem
|
||||
- kinery
|
||||
- goom
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -4,7 +4,7 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node build/",
|
||||
"dev": "vite dev",
|
||||
"dev": "vite dev --port 80",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "prettier --plugin-search-dir . --check .",
|
||||
|
|
@ -24,6 +24,9 @@
|
|||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@sveltejs/adapter-node": "^1.0.0-next.96"
|
||||
"@sveltejs/adapter-node": "^1.0.0-next.96",
|
||||
"bcrypt": "^5.1.0",
|
||||
"cookie": "^0.5.0",
|
||||
"mongodb": "^4.10.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,15 @@ body {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.line-clamp {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
@keyframes scrollbg {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,32 @@
|
|||
import { COOKIE_NAME } from "$lib/constants";
|
||||
import { getUserFromSession } from "$lib/database";
|
||||
|
||||
/** @type {import('@sveltejs/kit').Handle} */
|
||||
export async function handle({ event, resolve }) {
|
||||
if (event.url.pathname !== "/maintenance" && process.env.MAINTENANCE) {
|
||||
return new Response("", { status: 302, headers: { Location: "/maintenance" } });
|
||||
}
|
||||
|
||||
const cookie = event.cookies.get(COOKIE_NAME);
|
||||
if (!cookie) {
|
||||
if (event.routeId.startsWith("(app)")) return new Response("", { status: 302, headers: { Location: "/landing" } });
|
||||
return await resolve(event);
|
||||
}
|
||||
|
||||
let user = await getUserFromSession(cookie, event.request.headers.get("x-forwarded-for") || event.getClientAddress());
|
||||
if (!user) {
|
||||
event.cookies.delete(COOKIE_NAME, { secure: !!process.env.PRODUCTION });
|
||||
if (event.routeId.startsWith("(app)")) return new Response("", { status: 302, headers: { Location: "/landing" } });
|
||||
} else
|
||||
event.locals.user = {
|
||||
_id: user._id,
|
||||
username: user.username,
|
||||
currency: user.currency,
|
||||
thumbnails: {
|
||||
headshot: "/img/headshot.png",
|
||||
bodyshot: "https://media.tenor.com/Lo0GvkoTFR4AAAAd/xbox-xbox-avatar.gif"
|
||||
}
|
||||
};
|
||||
|
||||
return await resolve(event);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
<a href="/games/1">
|
||||
<img
|
||||
class="w-full min-h-28 max-h-28 mb-4"
|
||||
src="https://cdn.discordapp.com/attachments/1025864353452400812/1028718186700480513/058f09be8a19b8607d6fe50baa6659a1-AdBannerTemplate.png"
|
||||
alt="Your ad here."
|
||||
/>
|
||||
</a>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<div class="container flex bg-blue-600 outline outline-1 outline-blue-700 items-center justify-center mb-2 mt-[-0.5rem]">
|
||||
<p class="text-white">This is an example alert</p>
|
||||
</div>
|
||||
|
|
@ -1,7 +1,13 @@
|
|||
<div class="shadow-lg max-w-[15rem] p-2 rounded-lg border-2 border-gray-300 mt-2 mx-1 inline-block">
|
||||
<a href="/games/1">
|
||||
<img class="rounded mb-1" src="/img/background.png" alt="" />
|
||||
<p class="font-bold">Killing row simulator</p>
|
||||
<img class="mb-1" src="/img/background.png" alt="" />
|
||||
<p class="font-bold line-clamp">Killing row simulatorKilling row simulatorKilling row simulatorKilling row simulatorKilling row simulator</p>
|
||||
<p class="text-sm">0 Players Online</p>
|
||||
<div class="flex text-xs pt-1">
|
||||
<p class="px-1.5 bg-zinc-500 rounded-md text-white mr-0.5">Offline</p>
|
||||
<p class="px-1.5 bg-green-500 rounded-md text-white mr-0.5">Online</p>
|
||||
<p class="px-1.5 bg-blue-500 rounded-md text-white mr-0.5">2015</p>
|
||||
<p class="px-1.5 bg-blue-500 rounded-md text-white mr-0.5">2013</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<div class="max-w-[6rem] inline-block">
|
||||
<a href="/users/1">
|
||||
<img class="rounded-full border-2 border-grey-300 w-24 h-24 shadow-lg" src="" alt="" />
|
||||
<p class="text-center">Rowblox</p>
|
||||
<a href="/users/1/profile">
|
||||
<img class="rounded-full border-2 border-grey-600 w-24 h-24 shadow-lg" src="https://cdn.discordapp.com/attachments/1026648537741672490/1029210746501996616/2022.10-550.png" alt="" />
|
||||
<p class="text-center truncate">Only16Characters</p>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
<!-- https://github.com/BillBuilt/sveltekit-hcaptcha -->
|
||||
<script>
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import { browser } from "$app/environment";
|
||||
import { HCAPTCHA_SITEKEY } from "$lib/constants";
|
||||
|
||||
let hcaptcha;
|
||||
let hcaptchaWidgetID;
|
||||
|
||||
export let token = null;
|
||||
// forces 1-way binding
|
||||
let captchaToken;
|
||||
$: token = captchaToken;
|
||||
|
||||
export let isValid = false;
|
||||
// forces 1-way binding
|
||||
let captchaValid;
|
||||
$: isValid = captchaValid;
|
||||
|
||||
onMount(() => {
|
||||
setTimeout(function () {
|
||||
if (browser) {
|
||||
hcaptcha = window.hcaptcha;
|
||||
if (hcaptcha.render) {
|
||||
hcaptchaWidgetID = hcaptcha.render("hcaptcha", {
|
||||
sitekey: HCAPTCHA_SITEKEY,
|
||||
size: "normal",
|
||||
callback: onValidCaptcha,
|
||||
"error-callback": onErrorCaptcha,
|
||||
theme: "light"
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (browser) {
|
||||
hcaptcha = null;
|
||||
}
|
||||
});
|
||||
|
||||
function onValidCaptcha(e) {
|
||||
//console.log('verified event', e)
|
||||
captchaToken = e;
|
||||
captchaValid = true;
|
||||
}
|
||||
|
||||
function onErrorCaptcha(e) {
|
||||
//console.log('error event', {error: e.error})
|
||||
captchaToken = null;
|
||||
captchaValid = false;
|
||||
hcaptcha.reset(hcaptchaWidgetID);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<script src="https://js.hcaptcha.com/1/api.js?render=explicit" async defer></script>
|
||||
</svelte:head>
|
||||
|
||||
<div id="hcaptcha" class="h-captcha" />
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
export const COOKIE_NAME = ".ROWBLOX_SESSION_DO_NOT_SHARE";
|
||||
export const USERNAME_REGEX = /[^A-Za-z0-9\-_ ]/g;
|
||||
export const MIN_USERNAME_LENGTH = 3;
|
||||
export const MAX_USERNAME_LENGTH = 16;
|
||||
export const MIN_PASSWORD_LENGTH = 3;
|
||||
export const INVITE_KEY_PREFIX = "rowblox-";
|
||||
export const SESSION_EXPIRE = 604800000;
|
||||
export const HCAPTCHA_SITEKEY = "be5c40c1-13db-423c-878e-f3428e9fc841";
|
||||
export const RANKS = {
|
||||
0: "Banned",
|
||||
1: "Member",
|
||||
2: "Booster",
|
||||
3: "Administrator",
|
||||
4: "Developer",
|
||||
5: "NO_ACCESS_MAX_PERMISSIONS"
|
||||
};
|
||||
export const GAME_VERSIONS = {
|
||||
0: "2013",
|
||||
1: "2015"
|
||||
};
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
import { MongoClient } from "mongodb";
|
||||
import { hash, compare } from "bcrypt";
|
||||
import { prerendering } from "$app/environment";
|
||||
import { randomBytes, randomUUID } from "crypto";
|
||||
import { INVITE_KEY_PREFIX, SESSION_EXPIRE } from "$lib/constants";
|
||||
|
||||
if (!process.env.MONGO_URL) throw new Error("Missing MONGO_URL env variable!");
|
||||
|
||||
const client = new MongoClient(process.env.MONGO_URL);
|
||||
if (!prerendering) await client.connect();
|
||||
|
||||
const db = await client.db("rowblox");
|
||||
const users = await db.collection("users");
|
||||
const games = await db.collection("games");
|
||||
const assets = await db.collection("assets");
|
||||
const invites = await db.collection("invites");
|
||||
const avatars = await db.collection("avatars");
|
||||
const sessions = await db.collection("sessions");
|
||||
const friends = await db.collection("friends");
|
||||
|
||||
async function inc(collection) {
|
||||
await collection.updateOne({ _id: "_inc" }, { $inc: { _inc: 1 } });
|
||||
return (await collection.findOne({ _id: "_inc" }))._inc;
|
||||
}
|
||||
|
||||
async function hashPassword(plaintext) {
|
||||
return new Promise((resolve, reject) => {
|
||||
hash(plaintext, 10, (err, hash) => resolve(hash));
|
||||
});
|
||||
}
|
||||
|
||||
export async function compareHash(plaintext, hash) {
|
||||
return new Promise((resolve, reject) => {
|
||||
compare(plaintext, hash, (err, result) => resolve(result));
|
||||
});
|
||||
}
|
||||
|
||||
function genSession() {
|
||||
return randomBytes(64).toString("base64");
|
||||
}
|
||||
|
||||
function genInvite() {
|
||||
return `${INVITE_KEY_PREFIX}${randomUUID()}`;
|
||||
}
|
||||
|
||||
export async function createUser(username, password, lastip) {
|
||||
const _id = await inc(users);
|
||||
|
||||
const user = {
|
||||
_id,
|
||||
username,
|
||||
password: await hashPassword(password),
|
||||
lastip,
|
||||
currency: 100,
|
||||
joinDate: Date.now(),
|
||||
lastOnline: Date.now(),
|
||||
rank: 1,
|
||||
activity: 1,
|
||||
pendingFriendRequests: [],
|
||||
bio: "This is my Rowblox profile!"
|
||||
};
|
||||
|
||||
const avatar = {
|
||||
_id,
|
||||
BodyColors: {
|
||||
HeadColor: 1,
|
||||
TorsoColor: 1,
|
||||
LeftArmColor: 1,
|
||||
RightArmColor: 1,
|
||||
LeftLegColor: 1,
|
||||
RightLegColor: 1
|
||||
},
|
||||
Wearing: [],
|
||||
Inventory: []
|
||||
};
|
||||
|
||||
await Promise.all([avatars.insertOne(avatar), await users.insertOne(user)]);
|
||||
|
||||
return _id;
|
||||
}
|
||||
|
||||
export async function createSession(_id, lastip) {
|
||||
const user = await users.findOne({ _id });
|
||||
if (!user) return false;
|
||||
|
||||
const session = genSession();
|
||||
|
||||
await sessions.insertOne({
|
||||
_id: session,
|
||||
owner: _id,
|
||||
expires: Date.now() + SESSION_EXPIRE
|
||||
});
|
||||
|
||||
await users.updateOne({ _id: user._id }, { $set: { lastip } });
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function getUserFromSession(session, lastip) {
|
||||
const sessionDocument = await sessions.findOne({ _id: session });
|
||||
if (!sessionDocument) return false;
|
||||
|
||||
const user = await users.findOne({ _id: sessionDocument.owner });
|
||||
if (!user) return false;
|
||||
|
||||
await users.updateOne({ _id: user._id }, { $set: { lastip } });
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function deleteSession(session, lastip) {
|
||||
const sessionDocument = await sessions.findOne({ _id: session });
|
||||
if (!sessionDocument) return false;
|
||||
|
||||
const user = await users.findOne({ _id: sessionDocument.owner });
|
||||
if (!user) return false;
|
||||
|
||||
await users.updateOne({ _id: user._id }, { $set: { lastip } });
|
||||
|
||||
return await sessions.deleteOne({ _id: session });
|
||||
}
|
||||
|
||||
export async function getUser(query, projection) {
|
||||
const user = await users.findOne(query, { projection });
|
||||
if (!user) return false;
|
||||
|
||||
return user;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import crypto from "crypto";
|
||||
|
||||
const privatekey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
${process.env.PRIVATE_KEY}
|
||||
MIICXgIBAAKBgQDJ9itcSsBa9FNyE1jBn4OBfOYsMSSJWyi0Jgn6qXNvSzLks7INwmkqejrEc4WOdUNjQ6XaoAK/vmBf7oMQJ+xuDl47rYVqLGW0JAKPSbn0QA19bmr3eSIyUeqrhfwRGn4YLBDm6CMu+m8P/VaDU1qSxPd2PuIpLmTlKFMcF+HWEQIDAQABAoGBAL49+x5W89c5q5kbjFHnlpLVOmSKbiZNDoyUAHZ0RF6j8W7prmGzrijrNoxzXW2SHEZXJNZKQAyqolH7dM41LUnaIWizNoIkuJKIb+HWzQlnu75KLNyVDtlMyTNnwTfHkAQ6vmRv8f8S74ZXlj+SKNVjd0p0R+TSqh4NHzNZkNC5AkEA33wCzFtvZJv5cVbc8Ak8VmmSdoPN9HBHuJ1BJ/VTYrd9NmGoxbb2Ixfc3kVItD1NMLs9Jo3x8VKyC0fP90KAIwJBAOdYgtK4QGJHco48J37K3g9r4Kjidv933ADnkZefhTGE4ycakv5gcvnMWbcjHRPv3a7r39+ukgsbNB7BdyKWWjsCQBsewHQuMGFkMCwZ32vdow3Vd+mb6xVbvshfhPWlZr4XCEHeLg34OvxdO/dZLw54VfKw9iXEmfSwFV0bFNiroEMCQQCB05AHBNNM09+bpnJbmykm6lk3LW+uSesyrsFrn1+1vGdlSGp5SlL7kAxA0/m7eH6lbUVDV8opZWjIYbWjuVCFAkEAv8rLnjBwqbNY1O67a4rXh4DJZ7lHm5meX7+YcW5haB4GGyZpikVXYEcfwcM5A03dTKgljLbcDT8diO5RTOsDEg==
|
||||
-----END RSA PRIVATE KEY-----`;
|
||||
|
||||
export default function (input) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
import { page } from "$app/stores";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$page.status == 404 ? "Page Not Found" : "Unexpected Error"} - Rowblox</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="mx-auto rounded-lg shadow border border-gray-300 max-w-2xl text-center divide-y">
|
||||
<div class="p-2">
|
||||
<img class="mx-auto mb-2" src="/img/error.png" alt="" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
/** @type {import('./$types').LayoutServerLoad} */
|
||||
export function load({ locals }) {
|
||||
return { user: locals.user };
|
||||
}
|
||||
|
|
@ -1,3 +1,17 @@
|
|||
<script>
|
||||
import Alert from "$lib/components/Alert.svelte";
|
||||
import Banner from "$lib/components/AdBanner.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
/** @type {import('./$types').LayoutData} */
|
||||
export let data;
|
||||
|
||||
async function logout() {
|
||||
const response = await fetch("/logout", { method: "GET" });
|
||||
await goto("/landing");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="navbar scrolling-background text-lg py-1 text-white">
|
||||
<div class="container flex items-center justify-end">
|
||||
<a href="/">
|
||||
|
|
@ -5,33 +19,31 @@
|
|||
</a>
|
||||
<a class="mr-7" href="/games">Games</a>
|
||||
<a class="mr-7" href="/catalog">Catalog</a>
|
||||
<a class="mr-7" href="/forums">Forums</a>
|
||||
<a class="mr-7" href="https://discord.gg/3wRpA5bzU9">Discord</a>
|
||||
<input class="rounded px-2 border-2 border-grey-300 text-black flex-grow mr-10" type="text" name="search" id="search" placeholder="Search" />
|
||||
<a class="mr-4 px-2 py-1 flex items-center rounded bg-gradient-to-tr from-blue-700 to-blue-600 outline outline-blue-800 outline-1 shadow-md hover:shadow-xl" href="/my/money">
|
||||
<a class="mr-1 px-2 py-1 flex items-center rounded bg-gradient-to-tr from-blue-700 to-blue-600 outline outline-blue-800 outline-1 shadow-md hover:shadow-xl" href="/my/money">
|
||||
<img class="h-6 mr-1.5" src="/img/rowbux.png" alt="" />
|
||||
100
|
||||
</a>
|
||||
<p class="text-2xl mb-0.5">|</p>
|
||||
<div class="ml-2 relative inline-block dropdown">
|
||||
<button class="hover:text-zinc-200" href="/my/profile"
|
||||
>WWWWWWWWWWWWWWWW
|
||||
<div class="pl-1.5 ml-1 relative inline-block dropdown">
|
||||
<button class="hover:text-zinc-200 truncate" href="/my/profile"
|
||||
>{data.user?.username}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4 inline-block">
|
||||
<path fill-rule="evenodd" d="M12.53 16.28a.75.75 0 01-1.06 0l-7.5-7.5a.75.75 0 011.06-1.06L12 14.69l6.97-6.97a.75.75 0 111.06 1.06l-7.5 7.5z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="absolute bg-white text-center text-black w-36 right-0 origin-top-right rounded border border-gray-300 divide-y shadow-lg hidden dropdown-content">
|
||||
<div>
|
||||
<a class="block px-4 py-0.5 text-gray-700" href="/my/profile">Profile</a>
|
||||
<a class="block px-4 py-0.5 text-gray-700" href="/users/{data.user?._id}/profile">Profile</a>
|
||||
<a class="block px-4 py-0.5 text-gray-700" href="/my/settings">Settings</a>
|
||||
</div>
|
||||
<a class="block px-4 py-0.5 text-red-700" href="/logout">Logout</a>
|
||||
<span class="block px-4 py-0.5 text-red-700 hover:cursor-pointer" on:click={logout}>Logout</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="navbar bg-blue-900 text-base py-0.5 text-white">
|
||||
<div class="navbar bg-gray-800 text-base py-0.5 text-white">
|
||||
<div class="container flex items-center">
|
||||
<a class="mr-6" href="/my/avatar">Avatar</a>
|
||||
<a class="mr-6" href="/my/friends">Friends</a>
|
||||
|
|
@ -49,6 +61,23 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-black container mt-4">
|
||||
<div class="text-black container mt-4 min-h-[100vh]">
|
||||
<Alert />
|
||||
<!--<Banner />-->
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div class="flex text-white items-center justify-start scrolling-background mt-4">
|
||||
<div class="container">
|
||||
<div class="flex text-center py-2 border-b border-blue-500">
|
||||
<a class="text-lg flex-auto" href="/about/tos">Terms of Service</a>
|
||||
<a class="text-lg flex-auto" href="/about/rules">Rules</a>
|
||||
<a class="text-lg flex-auto" href="/about/privacy">Privacy Policy</a>
|
||||
<a class="text-lg flex-auto" href="/about/credits">Credits</a>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<img class="w-72 mr-6 justify-start inline saturate-150" src="/img/banner.png" alt="" />
|
||||
<h1 class="justify-end inline">©{new Date().getFullYear()} Rowblox Corporation. Rest in peace popbob 🙏🙏🙏</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import Game from "$lib/components/GameListed.svelte";
|
||||
import Friend from "$lib/components/UserListed.svelte";
|
||||
import { page } from "$app/stores";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -8,26 +9,16 @@
|
|||
</svelte:head>
|
||||
|
||||
<div class="flex items-center">
|
||||
<img class="rounded-full border-2 border-grey-300 w-36 h-36 mx-2 shadow-lg" src="" alt="" />
|
||||
<p class="mx-2 font-bold text-3xl">Hello, Only16Characters!</p>
|
||||
<img class="rounded-full border-2 border-grey-600 w-36 h-36 mx-2 shadow-lg" src={$page.data.user?.thumbnails.headshot} alt="" />
|
||||
<p class="mx-2 font-bold text-3xl">Hello, {$page.data.user?.username}!</p>
|
||||
</div>
|
||||
|
||||
<div class="my-2">
|
||||
<div class="my-2 min-h-[11rem]">
|
||||
<div class="flex items-center mb-2">
|
||||
<p class="font-bold text-2xl my-1">Friends</p>
|
||||
<span class="flex-grow" />
|
||||
<a class="text-lg px-3 py-0.5 rounded border-2 border-blue-500 shadow-lg cursor-pointer hover:bg-blue-200 text-blue-500" href="/my/friends">View All</a>
|
||||
</div>
|
||||
<Friend />
|
||||
<Friend />
|
||||
<Friend />
|
||||
<Friend />
|
||||
<Friend />
|
||||
<Friend />
|
||||
<Friend />
|
||||
<Friend />
|
||||
<Friend />
|
||||
<Friend />
|
||||
</div>
|
||||
|
||||
<div class="my-2">
|
||||
|
|
@ -36,12 +27,4 @@
|
|||
<span class="flex-grow" />
|
||||
<a class="text-lg px-3 py-0.5 rounded border-2 border-blue-500 shadow-lg cursor-pointer hover:bg-blue-200 text-blue-500" href="/games">View All</a>
|
||||
</div>
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,13 +13,3 @@
|
|||
<button class="text-lg px-3 py-0.5 rounded border-2 border-blue-500 shadow-lg cursor-pointer hover:bg-blue-200 text-blue-500 mr-2">Search</button>
|
||||
<a class="text-lg px-3 py-0.5 rounded border-2 border-blue-500 shadow-lg cursor-pointer hover:bg-blue-200 text-blue-500" href="/games/create">Create Game</a>
|
||||
</div>
|
||||
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
<Game />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
import { error } from "@sveltejs/kit";
|
||||
|
||||
/** @type {import('./$types').PageServerLoad} */
|
||||
export async function load({ params }) {
|
||||
throw error(404, "That game doesn't exist");
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
<script>
|
||||
import UserListed from "$lib/components/UserListed.svelte";
|
||||
export let game;
|
||||
let state = 0;
|
||||
|
||||
function updateState(newState) {
|
||||
state = newState;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{game?.name} - Rowblox</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="mx-auto rounded-lg shadow border border-gray-300 p-4 divide-y">
|
||||
<div class="flex">
|
||||
<img class="float-left h-[22.5rem]" alt="" src="/img/background.png" />
|
||||
<div class="w-full flex flex-col pl-4">
|
||||
<div class="max-w-[22.9rem]">
|
||||
<p class="text-3xl font-bold truncate">{game?.name}</p>
|
||||
<p class="text-zinc-500">By <a class="font-bold text-blue-500" href="/users/{game?.creator?._id}">{game?.creator?.username}</a></p>
|
||||
</div>
|
||||
<div class="flex text-sm pt-1">
|
||||
<p class="px-2 bg-zinc-500 rounded-md text-white mr-1">Offline</p>
|
||||
<p class="px-2 bg-green-500 rounded-md text-white mr-1">Online</p>
|
||||
<p class="px-2 bg-blue-500 rounded-md text-white mr-1">2015</p>
|
||||
<p class="px-2 bg-blue-500 rounded-md text-white mr-1">2013</p>
|
||||
</div>
|
||||
<span class="flex-grow" />
|
||||
<div class="flex">
|
||||
<a class="flex-auto text-lg text-blue-500 px-4 py-0.5 rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500 mr-0.5 mb-1 text-center" href="/games/{game?.id}/edit">
|
||||
Edit
|
||||
</a>
|
||||
<button class="flex-auto text-lg text-blue-500 px-4 py-0.5 rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500 ml-0.5 mb-1">Host</button>
|
||||
</div>
|
||||
<button class="text-2xl text-blue-500 px-4 py-2 rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500">Play</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4">
|
||||
<div class="flex flex-row text-xl text-center text-black">
|
||||
<button
|
||||
class="flex-auto {state == 0 && 'font-bold text-black text-opacity-30'}"
|
||||
on:click={() => {
|
||||
updateState(0);
|
||||
}}>About</button
|
||||
>
|
||||
<button
|
||||
class="flex-auto {state == 1 && 'font-bold text-black text-opacity-30'}"
|
||||
on:click={() => {
|
||||
updateState(1);
|
||||
}}>Store</button
|
||||
>
|
||||
<button
|
||||
class="flex-auto {state == 2 && 'font-bold text-black text-opacity-30'}"
|
||||
on:click={() => {
|
||||
updateState(2);
|
||||
}}>Players</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if state == 0}
|
||||
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4 divide-y">
|
||||
<div class="pb-2">
|
||||
<p class="px-2 py-1 font-bold text-xl">Description</p>
|
||||
<p class="px-2 text-black text-opacity-80">Test Game</p>
|
||||
</div>
|
||||
<div class="flex flex-row pb-2">
|
||||
<div class="mx-4 mt-4 text-center flex-grow">
|
||||
<p class="text-zinc-500 inline-block">Players</p>
|
||||
<p class="">0</p>
|
||||
</div>
|
||||
<div class="mx-4 mt-4 text-center flex-grow">
|
||||
<p class="text-zinc-500 inline-block">Visits</p>
|
||||
<p class="">0</p>
|
||||
</div>
|
||||
<div class="mx-4 mt-4 text-center flex-grow">
|
||||
<p class="text-zinc-500 inline-block">Created</p>
|
||||
<p class="">10/10/22</p>
|
||||
</div>
|
||||
<div class="mx-4 mt-4 text-center flex-grow">
|
||||
<p class="text-zinc-500 inline-block">Updated</p>
|
||||
<p class="">10/10/22</p>
|
||||
</div>
|
||||
<div class="mx-4 mt-4 text-center flex-grow">
|
||||
<p class="text-zinc-500 inline-block">Max Players</p>
|
||||
<p class="">No Limit</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if state == 1}
|
||||
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4">
|
||||
<p class="px-2 py-1 font-bold text-xl">Gamepasses</p>
|
||||
<p class="px-2 text-black text-opacity-80">This store does not have any gamepasses for sale.</p>
|
||||
</div>
|
||||
{:else if state == 2}
|
||||
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4">
|
||||
<p class="px-2 py-1 font-bold text-xl">Players</p>
|
||||
<p class="px-2 text-black text-opacity-80">This game does not have any players.</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4">
|
||||
<p class="px-2 py-1 font-bold text-xl mb-1">Players</p>
|
||||
<UserListed />
|
||||
<UserListed />
|
||||
<UserListed />
|
||||
<UserListed />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -2,30 +2,40 @@
|
|||
<title>Invites - Rowblox</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<h1 class="text-4xl mt-32 text-bold font-bold">Invite Keys</h1>
|
||||
<p>"keys can be created every 7 days. Creating a key costs 250 Rowbux."</p>
|
||||
<button class=" text-white px-4 py-1 w-full rounded shadow-lg border-2 border-white bg-green-500 hover:bg-green-400">Create a Key</button>
|
||||
<div>
|
||||
<table class="table-auto mx-auto text-semibold font-bold mt-4 text-1.5xl w-96 h-20 rounded shadow-lg border-2">
|
||||
<thead>
|
||||
<tr class="divide-x">
|
||||
<th>Key</th>
|
||||
<th>Created</th>
|
||||
<th>Uses</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="divide-x">
|
||||
<td>Keytest1</td>
|
||||
<td>10/8/22</td>
|
||||
<td>100</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="flex place-content-between">
|
||||
<div>
|
||||
<h1 class="text-3xl text-bold font-bold">Invite Keys</h1>
|
||||
<p>Invite Keys can be created every 7 days for 250 Rowbux each.</p>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<button class="my-auto px-3 py-1 rounded border-2 border-green-500 text-green-500 shadow-lg hover:bg-green-200">Create Invite Key</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<table class="table-auto text-center w-full border-2 border-gray-300 mt-2 divide-y">
|
||||
<thead>
|
||||
<tr class="divide-x">
|
||||
<th>Invite Key</th>
|
||||
<th>Claimed By</th>
|
||||
<th>Created</th>
|
||||
<th>Revoke</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
<tr class="divide-x">
|
||||
<td class="truncate">jahseh-45c61898-ddbb-414c-9cc2-4e6a35f164c8</td>
|
||||
<td>Nobody</td>
|
||||
<td>{new Date().toLocaleString()}</td>
|
||||
<td><button class="my-1 px-6 py-1 rounded border-2 border-red-500 text-red-500 shadow-lg hover:bg-red-200">Revoke</button></td>
|
||||
</tr>
|
||||
<tr class="divide-x">
|
||||
<td class="truncate">jahseh-67cb4676-071a-4160-8f18-af494ff15b80</td>
|
||||
<td><a class="text-blue-500" href="/users/1">Rowblox</a></td>
|
||||
<td>{new Date(1665297610952).toLocaleString()}</td>
|
||||
<td><button class="my-1 px-6 py-1 rounded border-2 border-red-300 text-red-300 shadow-lg cursor-default disabled">Revoke</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import { error } from "@sveltejs/kit";
|
||||
import { getUser } from "$lib/database";
|
||||
|
||||
/** @type {import('./$types').PageServerLoad} */
|
||||
export async function load({ params }) {
|
||||
const user = await getUser({ _id: Number(params.id) }, { username: true, currency: true, joinDate: true, lastOnline: true, rank: true, activity: true, bio: true });
|
||||
if (!user) throw error(404, "That user doesn't exist");
|
||||
|
||||
return { profile: user };
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<script>
|
||||
export let data;
|
||||
|
||||
const joinDate = new Date(data.profile?.joinDate);
|
||||
const lastOnline = new Date(data.profile?.lastOnline);
|
||||
</script>
|
||||
|
||||
<div class="mx-auto rounded-lg flex shadow border border-gray-300 px-2 py-1.5">
|
||||
<img class="rounded-full border-2 border-grey-600 h-28 shadow-lg mr-2" src="/img/headshot.png" alt="" />
|
||||
<div class="flex flex-col place-content-between pt-2 pb-1.5">
|
||||
<p class="text-3xl font-bold">{data.profile?.username}</p>
|
||||
<div class="flex text-lg">
|
||||
<p class="mr-4"><a class="font-bold" href="/users/id/friends">0</a> Friends</p>
|
||||
<p class="mr-4"><a class="font-bold" href="/users/id/followers">0</a> Followers</p>
|
||||
<p><a class="font-bold" href="/users/id/following">0</a> Following</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="flex-grow" />
|
||||
<div class="flex place-content-between place-self-end py-1">
|
||||
<a class="mr-2 px-3 py-1 rounded border-2 border-gray-500 text-gray-500 shadow-lg hover:bg-gray-200" href="/users/id/message">Message</a>
|
||||
<button class="px-3 py-1 rounded border-2 border-blue-500 text-blue-500 shadow-lg hover:bg-blue-200">Add Friend</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row my-2">
|
||||
<div class="w-full rounded-lg shadow border border-gray-300 p-4">
|
||||
<p class="text-2xl font-bold">About</p>
|
||||
<div class="divide-y">
|
||||
<img class="mx-auto w-96" src="/img/bodyshot.png" alt="" />
|
||||
<p class="text-center py-2">{data.profile?.bio}</p>
|
||||
<div class="flex flex-row text-center pt-2">
|
||||
<div class="flex flex-col mx-auto">
|
||||
<p class="font-bold text-zinc-400">Join Date</p>
|
||||
<p>{joinDate.getMonth() + 1}/{joinDate.getDate()}/{joinDate.getFullYear()}</p>
|
||||
</div>
|
||||
<div class="flex flex-col mx-auto">
|
||||
<p class="font-bold text-zinc-400">Last Online</p>
|
||||
<p>{lastOnline.getMonth() + 1}/{lastOnline.getDate()}/{lastOnline.getFullYear()}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full ml-1">
|
||||
<div class="border border-gray-300 rounded-lg w-full flex-auto mb-1 shadow p-4">
|
||||
<p class="text-2xl font-bold">Games</p>
|
||||
</div>
|
||||
<div class="border border-gray-300 rounded-lg w-full flex-auto shadow p-4">
|
||||
<p class="text-2xl font-bold">Friends</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { invalid, redirect } from "@sveltejs/kit";
|
||||
import { getUser, compareHash, createSession } from "$lib/database";
|
||||
import { MIN_USERNAME_LENGTH, MAX_USERNAME_LENGTH, USERNAME_REGEX, MIN_PASSWORD_LENGTH, INVITE_KEY_PREFIX, COOKIE_NAME } from "$lib/constants";
|
||||
|
||||
/** @type {import('./$types').Actions} */
|
||||
export const actions = {
|
||||
default: async ({ cookies, request, getClientAddress }) => {
|
||||
const session = cookies.get(COOKIE_NAME);
|
||||
if (session) throw redirect(302, "/");
|
||||
|
||||
const data = await request.formData();
|
||||
const username = data.get("username");
|
||||
const password = data.get("password");
|
||||
|
||||
const user = await getUser({ username }, { password: true });
|
||||
if (!user) return invalid(400, { error: "username" });
|
||||
|
||||
const correctPassword = await compareHash(password, user.password);
|
||||
if (!correctPassword) return invalid(400, { error: "password" });
|
||||
|
||||
cookies.set(COOKIE_NAME, await createSession(user._id, request.headers.get("x-forwarded-for") || getClientAddress()), { secure: !!process.env.PRODUCTION });
|
||||
throw redirect(302, "/");
|
||||
}
|
||||
};
|
||||
|
|
@ -1,12 +1,35 @@
|
|||
<script>
|
||||
/** @type {import('./$types').ActionData} */ export let form;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Login - Rowblox</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-lg mx-auto text-center text-white">
|
||||
<img class="mx-auto" src="/favicon.png" alt="" />
|
||||
<form method="POST" class="max-w-lg mx-auto text-center text-white">
|
||||
<img class="mx-auto mb-2" src="/favicon.png" alt="" />
|
||||
<div class="flex flex-col">
|
||||
<input class="rounded-lg w-72 px-4 border-2 border-blue-500 text-black mx-auto my-1.5 py-2" type="username" name="username" id="username" placeholder="Username" />
|
||||
<input class="rounded-lg w-72 px-4 border-2 border-blue-500 text-black mx-auto my-1.5 py-2" type="password" name="password" id="password" placeholder="Password" />
|
||||
<input
|
||||
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'username' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
|
||||
type="username"
|
||||
name="username"
|
||||
id="username"
|
||||
placeholder="Username"
|
||||
required
|
||||
value={form?.username ?? ""}
|
||||
/>
|
||||
<input
|
||||
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'password' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
|
||||
type="password"
|
||||
name="password"
|
||||
id="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button class="my-1.5 text-xl text-white px-4 py-1 w-full rounded shadow-lg border-2 border-blue-500">Login</button>
|
||||
</div>
|
||||
{#if form?.message}
|
||||
<p class="rounded w-[19rem] px-4 border-[3px] border-red-500 text-red-500 mx-auto my-1.5 py-1.5 hover:bg-opacity-50 hover:bg-red-500">{form?.message}</p>
|
||||
{/if}
|
||||
<div class="h-captcha" data-sitekey="30000000-ffff-ffff-ffff-000000000003" />
|
||||
<button class="my-1.5 text-xl text-white px-4 py-1.5 w-[19rem] rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500">Login</button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import { deleteSession } from "$lib/database";
|
||||
import { COOKIE_NAME } from "$lib/constants";
|
||||
|
||||
/** @type {import('./$types').RequestHandler} */
|
||||
export async function GET({ request, cookies, getClientAddress }) {
|
||||
const session = cookies.get(COOKIE_NAME);
|
||||
if (!session)
|
||||
return new Response("", {
|
||||
status: 302,
|
||||
headers: { location: "/landing" }
|
||||
});
|
||||
|
||||
cookies.delete(COOKIE_NAME, { secure: !!process.env.PRODUCTION });
|
||||
await deleteSession(session, request.headers.get("x-forwarded-for") || getClientAddress());
|
||||
|
||||
return new Response("", {
|
||||
status: 302,
|
||||
headers: { location: "/landing" }
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
import { invalid, redirect } from "@sveltejs/kit";
|
||||
import { createUser, createSession, getUser } from "$lib/database";
|
||||
import { MIN_USERNAME_LENGTH, MAX_USERNAME_LENGTH, USERNAME_REGEX, MIN_PASSWORD_LENGTH, INVITE_KEY_PREFIX, COOKIE_NAME, HCAPTCHA_SITEKEY } from "$lib/constants";
|
||||
|
||||
/** @type {import('./$types').Actions} */
|
||||
export const actions = {
|
||||
default: async ({ cookies, request, getClientAddress }) => {
|
||||
const session = cookies.get(COOKIE_NAME);
|
||||
if (session) throw redirect(302, "/landing");
|
||||
|
||||
const data = await request.formData();
|
||||
const username = data.get("username");
|
||||
const password = data.get("password");
|
||||
const confirm_password = data.get("confirm_password");
|
||||
const invite_key = data.get("invite_key");
|
||||
const hcaptcha_response = data.get("h-captcha-response");
|
||||
|
||||
if (username.length < MIN_USERNAME_LENGTH || username.length > MAX_USERNAME_LENGTH || new RegExp(USERNAME_REGEX).test(username))
|
||||
return invalid(400, {
|
||||
username,
|
||||
invite_key,
|
||||
error: "username"
|
||||
});
|
||||
|
||||
if (username.length < MIN_PASSWORD_LENGTH || confirm_password !== password)
|
||||
return invalid(400, {
|
||||
username,
|
||||
invite_key,
|
||||
error: "password"
|
||||
});
|
||||
|
||||
if (!invite_key.startsWith(INVITE_KEY_PREFIX))
|
||||
return invalid(400, {
|
||||
username,
|
||||
invite_key,
|
||||
error: "invite_key"
|
||||
});
|
||||
|
||||
if (!hcaptcha_response)
|
||||
return invalid(400, {
|
||||
username,
|
||||
invite_key,
|
||||
error: "hcaptcha"
|
||||
});
|
||||
|
||||
const existingUser = await getUser({ username }, { _id: true });
|
||||
if (existingUser)
|
||||
return invalid(400, {
|
||||
username,
|
||||
invite_key,
|
||||
error: "username"
|
||||
});
|
||||
|
||||
const hcaptcha = await fetch("https://hcaptcha.com/siteverify", {
|
||||
method: "POST",
|
||||
body: `response=${hcaptcha_response}&secret=${process.env.HCAPTCHA_SECRET}`,
|
||||
headers: { "content-type": "application/x-www-form-urlencoded" }
|
||||
});
|
||||
|
||||
const hcaptchaBody = await hcaptcha.json();
|
||||
if (!hcaptchaBody.success)
|
||||
return invalid(400, {
|
||||
username,
|
||||
invite_key,
|
||||
error: "hcaptcha"
|
||||
});
|
||||
|
||||
const user = await createUser(username, password, request.headers.get("x-forwarded-for") || getClientAddress());
|
||||
cookies.set(COOKIE_NAME, await createSession(user, request.headers.get("x-forwarded-for") || getClientAddress()), { secure: !!process.env.PRODUCTION });
|
||||
throw redirect(302, "/");
|
||||
}
|
||||
};
|
||||
|
|
@ -1,14 +1,58 @@
|
|||
<script>
|
||||
import HCaptcha from "../../../lib/components/hCaptcha.svelte";
|
||||
/** @type {import('./$types').ActionData} */ export let form;
|
||||
|
||||
let captchaValid = false;
|
||||
let captchaToken;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Register - Rowblox</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-lg mx-auto text-center text-white">
|
||||
<img class="mx-auto" src="/favicon.png" alt="" />
|
||||
<form method="POST" class="max-w-lg mx-auto text-center text-white">
|
||||
<img class="mx-auto mb-2" src="/favicon.png" alt="" />
|
||||
<div class="flex flex-col">
|
||||
<input class="rounded-lg w-72 px-4 border-2 border-blue-500 text-black mx-auto my-1.5 py-2" type="username" name="username" id="username" placeholder="Username" />
|
||||
<input class="rounded-lg w-72 px-4 border-2 border-blue-500 text-black mx-auto my-1.5 py-2" type="password" name="password" id="password" placeholder="Password" />
|
||||
<input class="rounded-lg w-72 px-4 border-2 border-blue-500 text-black mx-auto my-1.5 py-2" type="password" name="passwordconfirm" id="passwordconfirm" placeholder="Confirm Password" />
|
||||
<input class="rounded-lg w-72 px-4 border-2 border-blue-500 text-black mx-auto my-1.5 py-2" type="text" name="invite" id="invite" placeholder="Invite Key" />
|
||||
<input
|
||||
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'username' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
|
||||
required
|
||||
type="username"
|
||||
name="username"
|
||||
id="username"
|
||||
placeholder="Username"
|
||||
value={form?.username ?? ""}
|
||||
/>
|
||||
<input
|
||||
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'password' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
|
||||
required
|
||||
type="password"
|
||||
name="password"
|
||||
id="password"
|
||||
placeholder="Password"
|
||||
/>
|
||||
<input
|
||||
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'password' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
|
||||
required
|
||||
type="password"
|
||||
name="confirm_password"
|
||||
id="confirm_password"
|
||||
placeholder="Confirm Password"
|
||||
/>
|
||||
<input
|
||||
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'invite_key' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto mt-0.5 mb-1.5 py-2"
|
||||
required
|
||||
type="text"
|
||||
name="invite_key"
|
||||
id="invite_key"
|
||||
placeholder="Invite Key"
|
||||
value={form?.invite_key ?? ""}
|
||||
/>
|
||||
</div>
|
||||
<button class="my-1.5 text-xl text-white px-4 py-1 w-full rounded shadow-lg border-2 border-blue-500">Register</button>
|
||||
</div>
|
||||
{#if form?.message}
|
||||
<p class="rounded w-[19rem] px-4 border-2 border-red-500 text-red-500 bg-red-500 mx-auto my-1.5 py-1.5 bg-opacity-50">{form?.message}</p>
|
||||
{/if}
|
||||
<div class={form?.error == "hcaptcha" && "border-2 border-red-500"}>
|
||||
<HCaptcha bind:token={captchaToken} bind:isValid={captchaValid} />
|
||||
</div>
|
||||
<button class="my-1.5 text-xl text-white px-4 py-1.5 w-[19rem] rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500">Register</button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
{"data":["0fb35a870ca24bea2a17020144335cd7","0fb35a870ca24bea2a17020144335cd7"]}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"data":["0.206.0pcplayer"]}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
{}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Loading…
Reference in New Issue