diff --git a/web/app/Http/Controllers/Auth/RegisterController.php b/web/app/Http/Controllers/Auth/RegisterController.php index 79cf753..8e46d50 100644 --- a/web/app/Http/Controllers/Auth/RegisterController.php +++ b/web/app/Http/Controllers/Auth/RegisterController.php @@ -98,7 +98,7 @@ class RegisterController extends Controller setcookie('gtok', $sc, time()+(345600*30), "/", $_POST['host']); - return Response()->json('good'); + return Response()->json(['message'=>'Success!', 'badInputs'=>[]]); } } diff --git a/web/app/Http/Controllers/Controller.php b/web/app/Http/Controllers/Controller.php index f2fe329..9f894ca 100644 --- a/web/app/Http/Controllers/Controller.php +++ b/web/app/Http/Controllers/Controller.php @@ -34,14 +34,39 @@ class Controller extends BaseController $array = $user->toArray(); + if ($user->Staff()) $array['power'] = $user->Staff()->power_level; + return Response()->json(["data"=>$array]); } - public function fetchCategories() { - - $categories = Category::get(); + public function fetchCategoriesFP() { + if (!isset($_COOKIE['gtok'])) {return Response()->json(["error"=>"No user."]);} + + $POST = $_COOKIE['gtok']; + + $user = User::where('token', $POST)->first(); + + if (!$user) {return Response()->json(["error"=>"No user."]);} + + if ($user->Staff() && $user->Staff()->power_level >= 2) {$categories = Category::get();}else{$categories = Category::where('staffOnly', '0')->get();} + + return Response()->json(["categories"=>$categories]); + } + + public function fetchCategories() { + + if (!isset($_COOKIE['gtok'])) {return Response()->json(["error"=>"No user."]);} + + $POST = $_COOKIE['gtok']; + + $user = User::where('token', $POST)->first(); + + if (!$user) {return Response()->json(["error"=>"No user."]);} + + $categories = Category::orderBy('staffOnly', 'desc')->get(); + + return Response()->json(["categories"=>$categories]); - return Response()->json(["data"=>$categories]); } public function fetchCategory($id) { @@ -50,7 +75,7 @@ class Controller extends BaseController if (!$category) {return Response()->json(false);} - $posts = $category->posts()->paginate(20); + $posts = $category->posts()->orderBy('pinned', 'desc')->orderBy('updated_at', 'desc')->paginate(20); foreach ($posts as &$post) { $post['creator'] = User::where('id', $post['creator_id'])->first(); @@ -67,9 +92,19 @@ class Controller extends BaseController $postA = $post->toArray(); - $postA['creator'] = User::where('id', $postA['creator_id'])->first();; + $realDate = explode('T', $postA['created_at'])[0]; - $replies = $post->replies()->paginate(10); + $postA['created_at'] = $realDate; + + $postA['creator'] = User::where('id', $postA['creator_id'])->first(); + + $replies = $post->replies()->orderBy('pinned', 'desc')->orderBy('created_at', 'asc')->paginate(10); + + foreach ($replies as &$reply) { + $creator = User::where('id', $reply['creator_id'])->first(); + $reply['created_at'] = explode('T', $reply['created_at'])[0]; + $reply['creator_name'] = $creator->username; + } return Response()->json(["post"=>$postA,"replies"=>$replies]); } @@ -124,7 +159,7 @@ class Controller extends BaseController Auth::login($user); - return Response()->json('good'); + return Response()->json(['message'=>'Success!', 'badInputs'=>[]]); } diff --git a/web/app/Http/Controllers/HomeController.php b/web/app/Http/Controllers/HomeController.php index 1c82426..cdea655 100644 --- a/web/app/Http/Controllers/HomeController.php +++ b/web/app/Http/Controllers/HomeController.php @@ -6,6 +6,7 @@ use App\Http\Controllers\Controller; use App\Providers\RouteServiceProvider; use App\Models\User; use App\Models\Post; +use App\Models\Reply; use App\Models\Category; use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Support\Facades\Hash; @@ -20,10 +21,10 @@ class HomeController extends Controller * * @return void */ - public function __construct() + /*public function __construct() { $this->middleware('auth'); - } + }*/ /** * Show the application dashboard. @@ -42,6 +43,7 @@ class HomeController extends Controller $valid = Validator::make($data, [ 'title' => ['required', 'string', 'min:3', 'max:38'], 'body' => ['required', 'string', 'min:3', 'max:380'], + 'category' => ['required'] ]); if ($valid->stopOnFirstFailure()->fails()) { @@ -56,16 +58,62 @@ class HomeController extends Controller if (!$user) {return Response()->json(['message'=>'User not found!', 'badInputs'=>['title']]);} + if (!isset($_POST['category'])) {return Response()->json(['message'=>'Category not found!', 'badInputs'=>['category']]);} + + $categoryId = $_POST['category']; + + $category = Category::where('id', $categoryId)->first(); + + if ($category->staffOnly == '1' && !$user->Staff()) {return Response()->json(['message'=>'You cant use that category.', 'badInputs'=>['category']]);} + $post = new Post; $post->title = $_POST['title']; $post->body = $_POST['body']; $post->creator_id = $_POST['creator_id']; - //will add category support later - $post->category_id = 1; - $post->category_type = 'App\Models\Category'; - $post->save(); + $category->posts()->save($post); - return Response()->json('good'); + return Response()->json(['message'=>'Success!', 'badInputs'=>[], 'post_id'=>$post->id]); + + } + + public function createReply($id) { + + $data = Request::all(); + + $valid = Validator::make($data, [ + 'body' => ['required', 'string', 'min:3', 'max:380'], + ]); + + if ($valid->stopOnFirstFailure()->fails()) { + $error = $valid->errors()->first(); + $messages = $valid->messages()->get('*'); + return Response()->json(['message'=>$error, 'badInputs'=>[array_keys($messages)]]); + } + + if (!isset($_COOKIE['gtok'])) {return Response()->json(["error"=>"No user."]);} + + $POST = $_COOKIE['gtok']; + + $meta = User::where('token', $POST)->first(); + + if (!isset($_POST['creator_id'])) {return Response()->json(['message'=>'System error', 'badInputs'=>['title']]);} + + $user = User::where('id', $_POST['creator_id'])->first(); + + if (!$user) {return Response()->json(['message'=>'User not found!', 'badInputs'=>['title']]);} + + $post = Post::where('id', $id)->first(); + + if (!$post) {return Response()->json(['message'=>'Post not found!', 'badInputs'=>['body']]);} + + if ($post->locked && $user->id != $meta->id) {return Response()->json(['message'=>'This post is locked!', 'badInputs'=>['body']]);} + + $reply = new Reply; + $reply->body = $_POST['body']; + $reply->creator_id = $user->id; + $post->replies()->save($reply); + + return Response()->json(['message'=>'Success!', 'badInputs'=>[], 'post_id'=>$post->id]); } } diff --git a/web/app/Models/Staff.php b/web/app/Models/Staff.php new file mode 100644 index 0000000..111c4e8 --- /dev/null +++ b/web/app/Models/Staff.php @@ -0,0 +1,14 @@ + 'datetime', ]; + + public function Staff() { + $staff = Staff::where('user_id', $this->id)->first(); + return $staff; + } + } diff --git a/web/database/migrations/2022_03_14_012005_create_staff_table.php b/web/database/migrations/2022_03_14_012005_create_staff_table.php new file mode 100644 index 0000000..4afa44c --- /dev/null +++ b/web/database/migrations/2022_03_14_012005_create_staff_table.php @@ -0,0 +1,35 @@ +id(); + $table->string('power_level')->default(1); + $table->string('user_id'); + $table->string('staff_title')->default('Moderator'); + /* Obviously this isn't the most elegant way of designing a staff system, but ill revamp it later down the road. */ + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('staff'); + } +} diff --git a/web/resources/js/helpers/Auth.js b/web/resources/js/helpers/Auth.js index 9d53939..7c1cb42 100644 --- a/web/resources/js/helpers/Auth.js +++ b/web/resources/js/helpers/Auth.js @@ -2,6 +2,7 @@ // Graphictoria 5 import axios from "axios"; +import { useHistory } from "react-router-dom"; import Config from '../config.js'; axios.defaults.withCredentials = true @@ -23,8 +24,9 @@ export function CreateAccount(form) badInputs=res.badInputs; resolve({message: res.message, inputs: res.badInputs}); return; + }else{ + resolve("good") } - resolve("good"); }).catch(error=>{console.log(error);}); }); @@ -44,8 +46,9 @@ export function LoginToAccount(form) { badInputs=res.badInputs; resolve({message: res.message, inputs: res.badInputs}); return; + }else{ + resolve("good") } - resolve("good"); }).catch(error=>{console.log(error);}); }); @@ -59,6 +62,7 @@ export function CreateForum(form) { return new Promise(async (resolve, reject)=>{ axios.post(`${protocol}apis.${url}/api/create/forum`, body, {headers: {'X-CSRF-TOKEN': document.querySelector(`meta[name="csrf-token"]`).content, "X-Requested-With":"XMLHttpRequest"}}).then(data=>{ + const history = useHistory(); const res = data.data; if (res.badInputs.length >= 1) { badInputs=res.badInputs; @@ -69,7 +73,7 @@ export function CreateForum(form) { }); } -export function LogoutOfAccount() { +/*export function LogoutOfAccount() { const body = form; var badInputs = []; @@ -79,4 +83,4 @@ export function LogoutOfAccount() { resolve("good"); }).catch(error=>{console.log(error);}); -} +}*/ diff --git a/web/resources/js/layouts/App.js b/web/resources/js/layouts/App.js index 18e08a5..e5d1e66 100644 --- a/web/resources/js/layouts/App.js +++ b/web/resources/js/layouts/App.js @@ -3,7 +3,7 @@ import React from 'react'; import { useState, useEffect } from "react"; import ReactDOM from "react-dom"; import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; -import { PageTransition } from '@steveeeie/react-page-transition'; +import { PageTransition, __esModule } from '@steveeeie/react-page-transition'; import { GuardProvider, GuardedRoute } from 'react-router-guards' import Config from '../config.js'; @@ -32,6 +32,7 @@ import Dashboard from '../pages/Dashboard.js'; import Forum from '../pages/Forum.js'; import Post from '../pages/Post.js'; import CreatePost from '../pages/CreatePost.js'; +import CreateReply from '../pages/CreateReply.js'; axios.defaults.withCredentials = true @@ -92,6 +93,9 @@ const App = () => { }else if (to.meta.guest) { if (!user) {next();} next.redirect(`/home`); + }else if (to.meta.staff) { + if (user && user.power) {next();} + next.redirect(`/`); } } @@ -145,6 +149,10 @@ const App = () => { + + + + diff --git a/web/resources/js/layouts/Card.js b/web/resources/js/layouts/Card.js index 3ecfc7c..5219e83 100644 --- a/web/resources/js/layouts/Card.js +++ b/web/resources/js/layouts/Card.js @@ -6,7 +6,7 @@ import React from 'react'; export const Card = (props) => { return (
-
+
{ props.children }
diff --git a/web/resources/js/pages/Auth/Login.js b/web/resources/js/pages/Auth/Login.js index 2b5bd3b..9edc6ca 100644 --- a/web/resources/js/pages/Auth/Login.js +++ b/web/resources/js/pages/Auth/Login.js @@ -2,7 +2,7 @@ // Graphictoria 5 import React, {useState} from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useHistory } from 'react-router-dom'; import ReCAPTCHA from 'react-google-recaptcha'; import { CreateAccount, LoginToAccount } from '../../Helpers/Auth'; @@ -11,8 +11,8 @@ import Loader from '../../Components/Loader'; const LoginForm = (props) => { const [waitingForSubmission, setWaitingForSubmission] = useState(false); - const [validity, setValidity] = useState({error: false, message: ``, inputs: []}); + const history = useHistory(); async function SubmitLogin(form) { @@ -22,8 +22,9 @@ const LoginForm = (props) => { setValidity({error: true, message:res.message, inputs: res.inputs}); setTimeout(()=>{setValidity({...validity, error: false, inputs: res.inputs});}, 4000); return; + }else{ + window.location.replace(`/home`); } - window.location.reload(); }).catch(error=>console.log(error)); setWaitingForSubmission(false); } diff --git a/web/resources/js/pages/Auth/Register.js b/web/resources/js/pages/Auth/Register.js index 2b49928..f623beb 100644 --- a/web/resources/js/pages/Auth/Register.js +++ b/web/resources/js/pages/Auth/Register.js @@ -2,7 +2,7 @@ // Graphictoria 5 import React, { useState } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useHistory } from 'react-router-dom'; import ReCAPTCHA from 'react-google-recaptcha'; @@ -34,8 +34,8 @@ const RegisterForm = (props) => { ]; const [waitingForSubmission, setWaitingForSubmission] = useState(false); - const [validity, setValidity] = useState({error: false, message: ``, inputs: []}); + const history = useHistory(); async function SubmitRegistration(form) { @@ -46,8 +46,9 @@ const RegisterForm = (props) => { setValidity({error: true, message:res.message, inputs: res.inputs}); setTimeout(()=>{setValidity({...validity, error: false, inputs: res.inputs});}, 4000); return; + }else{ + window.location.replace(`/home`); } - window.location.replace(`/home`); }).catch(error=>console.log(error)); setWaitingForSubmission(false); } diff --git a/web/resources/js/pages/CreatePost.js b/web/resources/js/pages/CreatePost.js index 9062e3d..9f82559 100644 --- a/web/resources/js/pages/CreatePost.js +++ b/web/resources/js/pages/CreatePost.js @@ -1,8 +1,8 @@ // © XlXi 2021 // Graphictoria 5 -import React, {useState} from 'react'; -import { Link } from 'react-router-dom'; +import React, {useEffect, useState} from 'react'; +import { Link, useHistory } from 'react-router-dom'; import ReCAPTCHA from 'react-google-recaptcha'; import { CreateAccount, LoginToAccount, CreateForum } from '../Helpers/Auth'; @@ -11,6 +11,7 @@ import { getCookie } from '../helpers/utils'; import axios from "axios"; import Config from '../config.js'; +import { Card, CardTitle } from '../Layouts/Card'; axios.defaults.withCredentials = true @@ -21,7 +22,16 @@ const CreatePost = (props) => { const [waitingForSubmission, setWaitingForSubmission] = useState(false); const [validity, setValidity] = useState({error: false, message: ``, inputs: []}); + const [categories, setCategoires] = useState({loading: true, categories: []}); const user = props.user; + const history = useHistory(); + + useEffect(async()=>{ + await axios.get(`${protocol}apis.${url}/fetch/categories/post`, null, {headers: {'X-CSRF-TOKEN': document.querySelector(`meta[name="csrf-token"]`).content, "X-Requested-With":"XMLHttpRequest"}}).then(data=>{ + const res = data.data; + setCategoires({loading: false, categories: res.categories}); + }).catch(error=>{console.log(error);}); + }, []); async function SubmitForm(form) { @@ -33,18 +43,18 @@ const CreatePost = (props) => { if (res.badInputs.length >= 1) { setValidity({error: true, message:res.message, inputs: res.badInputs}); setTimeout(()=>{setValidity({...validity, error: false, inputs: res.badInputs});}, 4000); - return; + }else{ + history.push(`/forum/post/${res.post_id}`); } - window.location.replace(`/forum`); }).catch(error=>{console.log(error);}); setWaitingForSubmission(false); } return ( - waitingForSubmission ? : -
-
-
+ waitingForSubmission && !categories.loading? : + + Create a new Post +
{validity.error?
@@ -56,6 +66,11 @@ const CreatePost = (props) => {
{e.preventDefault();SubmitForm(new FormData(e.target));}} class="fs"> input == `title`)? `is-invalid` : ``)}`} placeholder="Title" name="title"/> +
{

Before you make a post, be sure to read the rules.

-
-
+
); }; diff --git a/web/resources/js/pages/CreateReply.js b/web/resources/js/pages/CreateReply.js new file mode 100644 index 0000000..38abc4c --- /dev/null +++ b/web/resources/js/pages/CreateReply.js @@ -0,0 +1,88 @@ +// © XlXi 2021 +// Graphictoria 5 + +import React, {useEffect, useState} from 'react'; +import { Link, useHistory, useParams } from 'react-router-dom'; + +import ReCAPTCHA from 'react-google-recaptcha'; +import { CreateAccount, LoginToAccount, CreateForum } from '../Helpers/Auth'; +import Loader from '../Components/Loader'; +import { getCookie } from '../helpers/utils'; +import { Card, CardTitle } from '../Layouts/Card'; + +import axios from "axios"; +import Config from '../config.js'; + +axios.defaults.withCredentials = true + +var url = Config.BaseUrl.replace('http://', ''); +var protocol = Config.Protocol; + +const CreateReply = (props) => { + + const [waitingForSubmission, setWaitingForSubmission] = useState(false); + const [validity, setValidity] = useState({error: false, message: ``, inputs: []}); + const [post, setPost] = useState({loading: true, post: []}); + const user = props.user; + const postId = useParams().id; + const history = useHistory(); + + useEffect(async()=>{ + await axios.get(`${protocol}apis.${url}/fetch/post/${postId}`, {headers: {"X-Requested-With":"XMLHttpRequest"}}).then(data=>{ + if (!data.data) {history.push(`/forum`);} + const res = data.data; + setPost({loading: false, post: res.post}); + }).catch(error=>{console.log(error);}); + }, []); + + async function SubmitForm(form) + { + form.append('creator_id', user.id); + setWaitingForSubmission(true); + await axios.post(`${protocol}apis.${url}/api/create/reply/${post.post.id}`, form, {headers: {'X-CSRF-TOKEN': document.querySelector(`meta[name="csrf-token"]`).content, "X-Requested-With":"XMLHttpRequest"}}).then(data=>{ + const res = data.data; + console.log(res); + if (res.badInputs.length >= 1) { + setValidity({error: true, message:res.message, inputs: res.badInputs}); + setTimeout(()=>{setValidity({...validity, error: false, inputs: res.badInputs});}, 4000); + }else{ + history.push(`/forum/post/${res.post_id}`); + } + }).catch(error=>{console.log(error);}); + setWaitingForSubmission(false); + } + + return ( + waitingForSubmission && !post.loading? : + + Reply to '{post.post.title}' +
+
+ {validity.error? +
+
+

{validity.message}

+
+
+ : null} + {e.preventDefault();SubmitForm(new FormData(e.target));}} class="fs"> + +
+ +
+
+ +
+
+
Read the rules before replying!
+

Before you make a post, be sure to read the rules.

+
+
+
+ ); +}; + +export default CreateReply; \ No newline at end of file diff --git a/web/resources/js/pages/Forum.js b/web/resources/js/pages/Forum.js index ca5f2ff..80228c0 100644 --- a/web/resources/js/pages/Forum.js +++ b/web/resources/js/pages/Forum.js @@ -30,7 +30,7 @@ const Forum = (props) => { const fetchCategories = async () => { await axios.get(`${protocol}apis.${url}/fetch/categories`, {headers: {"X-Requested-With":"XMLHttpRequest"}}).then(data=>{ - setCategoires(data.data.data); + setCategoires(data.data.categories); }).catch(error=>{console.log(error);}); } @@ -61,7 +61,7 @@ const Forum = (props) => {

{category.description}

{user?
- Create a post + {category.staffOnly == 1 && !user.power ? null : Create a post}
: null}
@@ -87,8 +87,8 @@ const Forum = (props) => {
{post.title}
-

Posted by:

- {post.creator.username} +

Posted by:

+ {post.creator.username}
diff --git a/web/resources/js/pages/Post.js b/web/resources/js/pages/Post.js index c6b3f69..86a0a9c 100644 --- a/web/resources/js/pages/Post.js +++ b/web/resources/js/pages/Post.js @@ -23,11 +23,13 @@ const Post = (props) => { const [state, setState] = useState({offline: false, loading: true}); const [post, setPost] = useState({post: [], replies: {replies: [], meta: [], currentPage: 0}}); const user = props.user; + const history = useHistory(); const fetchPost = async () => { await axios.get(`${protocol}apis.${url}/fetch/post/${id}`, {headers: {"X-Requested-With":"XMLHttpRequest"}}).then(data=>{ - if (!data.data) {window.location.href=`/forum`;return;} - setPost({post: data.data.post, replies: {replies: data.data.replies.data, meta: data.data.replies, currentPage: 0}}); + if (!data.data) {history.push(`/forum`);} + const res = data.data; + setPost({post: res.post, replies: {replies: res.replies.data, meta: res.replies, currentPage: 0}}); }).catch(error=>{console.log(error);}); } @@ -42,18 +44,68 @@ const Post = (props) => { ? : -
-
- {/* Time&Date goes here. */} -
-
-

Posted by:

- {post.post.creator.username} +
+ {post.post.locked == 1? +
+
+

This post is locked!

+
+
+ : +
+
+ {user && user.username? Reply :

Sign in to reply!

} +
+
} + +
+
+
+

Post Title:

+

'{post.post.title}'

+
+
+

Date posted:

+

{post.post.created_at}

+
+
-

{post.post.body}

+
+

[Avatar.]

+ {post.post.creator.username} +
+
+

{post.post.body}

+
+
+

+ {post.replies.replies.length <= 0 && post.post.locked != 1?

There isn't any replies to this post yet!

: null} +
+ {post.replies.replies.map(reply=>( + +
+
+
+

Date posted:

+

{reply.created_at}

+
+
+
+
+
+

[Avatar.]

+ {reply.creator_name} +
+
+

{reply.body}

+
+
+
+
+ ))}
); diff --git a/web/resources/sass/Graphictoria.scss b/web/resources/sass/Graphictoria.scss index cb630df..476a232 100644 --- a/web/resources/sass/Graphictoria.scss +++ b/web/resources/sass/Graphictoria.scss @@ -898,6 +898,14 @@ a.list-group-item { flex-direction: column; } +.w-fit-content { + width: fit-content !important; +} + +.h-fit-content { + height: fit-content !important; +} + .mr-15 { margin-right: 15px; } @@ -914,6 +922,30 @@ p { font-size: 12px !important; } +.padding-none { + padding: 0px !important +} + +.text-left { + text-align: left !important; +} + +.text-center { + text-align: center !important; +} + +.text-right { + text-align: right !important; +} + +.mt-15 { + margin-top: 15px !important; +} + +.mb-15 { + margin-bottom: 15px !important; +} + .graphic-post { padding: 1rem 1rem; text-align: start; diff --git a/web/routes/apis.php b/web/routes/apis.php index 98e1b25..f68d7ce 100644 --- a/web/routes/apis.php +++ b/web/routes/apis.php @@ -29,6 +29,8 @@ Route::get('/games/metadata', 'GamesController@isAvailable'); Route::get('/fetch/categories', 'Controller@fetchCategories'); +Route::get('/fetch/categories/post', 'Controller@fetchCategoriesFP'); + Route::get('/fetch/category/{id}', 'Controller@fetchCategory'); Route::get('/fetch/posts/{id}', 'Controller@fetchPosts'); @@ -45,6 +47,8 @@ Route::post('/account/login', 'Controller@login'); Route::post('/api/create/forum', 'HomeController@createPost'); +Route::post('/api/create/reply/{id}', 'HomeController@createReply'); + Route::fallback(function(){ return response('{"errors":[{"code":404,"message":"NotFound"}]}', 404) ->header('Cache-Control', 'private')