From ea833465973ecb86c2a971988989afd9f085e6fa Mon Sep 17 00:00:00 2001 From: Casper JB Date: Tue, 4 Nov 2025 13:59:07 +0000 Subject: [PATCH 01/74] adding strict function typing and class:: --- api/controllers/SiteController.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/api/controllers/SiteController.php b/api/controllers/SiteController.php index 5f9233f..44c9734 100644 --- a/api/controllers/SiteController.php +++ b/api/controllers/SiteController.php @@ -9,13 +9,15 @@ use yii\filters\VerbFilter; use app\models\LoginForm; use app\models\ContactForm; +use yii\web\ErrorAction; +use yii\captcha\CaptchaAction; class SiteController extends Controller { /** * {@inheritdoc} */ - public function behaviors() + public function behaviors(): array { return [ 'access' => [ @@ -41,14 +43,14 @@ public function behaviors() /** * {@inheritdoc} */ - public function actions() + public function actions(): array { return [ 'error' => [ - 'class' => 'yii\web\ErrorAction', + 'class' => ErrorAction::class, ], 'captcha' => [ - 'class' => 'yii\captcha\CaptchaAction', + 'class' => CaptchaAction::class, 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, ], ]; @@ -59,7 +61,7 @@ public function actions() * * @return string */ - public function actionIndex() + public function actionIndex(): string { return $this->render('index'); } @@ -69,7 +71,7 @@ public function actionIndex() * * @return Response|string */ - public function actionLogin() + public function actionLogin(): Response|string { if (!Yii::$app->user->isGuest) { return $this->goHome(); @@ -91,7 +93,7 @@ public function actionLogin() * * @return Response */ - public function actionLogout() + public function actionLogout(): Response { Yii::$app->user->logout(); @@ -103,7 +105,7 @@ public function actionLogout() * * @return Response|string */ - public function actionContact() + public function actionContact(): Response|string { $model = new ContactForm(); if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) { @@ -121,7 +123,7 @@ public function actionContact() * * @return string */ - public function actionAbout() + public function actionAbout(): string { return $this->render('about'); } From 479cfa884627cf8fa333b0eed73afd442bc1cc44 Mon Sep 17 00:00:00 2001 From: Casper JB Date: Tue, 4 Nov 2025 13:59:31 +0000 Subject: [PATCH 02/74] setup react routes --- frontend/src/App.tsx | 35 ++++++++++++++++++----------------- frontend/src/main.tsx | 16 +++++++++------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7e48e0f..210f64b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,22 +1,23 @@ -import { useState } from "react"; - -export default function BackendTest() { - const [response, setResponse] = useState(""); - - async function testBackend() { - try { - const res = await fetch("http://127.0.0.1:8000/health/check"); - const json = await res.json(); - setResponse(JSON.stringify(json)); - } catch (err) { - console.error(err); - } - } +import { Routes, Route, Link } from 'react-router-dom' +import Home from './pages/Home' +import Test from './pages/Test.tsx' +import Categories from './pages/Categories.tsx' +function App() { return (
- -
{response}
+ + + + } /> + } /> + } /> +
- ); + ) } + +export default App diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index bef5202..ec904f5 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,12 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' +import React from 'react' +import ReactDOM from 'react-dom/client' +import { BrowserRouter } from 'react-router-dom' import App from './App.tsx' -createRoot(document.getElementById('root')!).render( - - - , +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + ) From 89bcc357453207a4ba8172acb77ff867241c3399 Mon Sep 17 00:00:00 2001 From: Casper JB Date: Tue, 4 Nov 2025 14:00:21 +0000 Subject: [PATCH 03/74] adding count controllers and loosening pagination rules for species controller --- api/controllers/PokemonSpeciesController.php | 19 ++++++++++++++++++- api/controllers/TeamsController.php | 5 +++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/api/controllers/PokemonSpeciesController.php b/api/controllers/PokemonSpeciesController.php index 8eccab6..2ae6b69 100644 --- a/api/controllers/PokemonSpeciesController.php +++ b/api/controllers/PokemonSpeciesController.php @@ -3,9 +3,26 @@ namespace app\controllers; use app\models\PokemonSpecies; -use yii\rest\ActiveController; +use app\rest\ActiveController; +use yii\helpers\ArrayHelper; class PokemonSpeciesController extends ActiveController { public $modelClass = PokemonSpecies::class; + + public function actions(): array + { + return ArrayHelper::merge(parent::actions(), [ + 'index' => [ + 'pagination' => [ + 'pageSizeLimit' => [1,10000], + ] + ] + ]); + } + + public function actionCount(): int + { + return $this->modelClass::find()->count(); + } } \ No newline at end of file diff --git a/api/controllers/TeamsController.php b/api/controllers/TeamsController.php index bfff78b..f928be9 100644 --- a/api/controllers/TeamsController.php +++ b/api/controllers/TeamsController.php @@ -9,4 +9,9 @@ class TeamsController extends ActiveController { public $modelClass = Team::class; + + public function actionUsersCount($userId): int + { + return $this->modelClass::find()->where(['user_id' => $userId])->count(); + } } \ No newline at end of file From 935e2bbea1324541076f6a802d8dca6aaeaf8cc5 Mon Sep 17 00:00:00 2001 From: Casper JB Date: Tue, 4 Nov 2025 14:01:37 +0000 Subject: [PATCH 04/74] adding package-lock.json to gitignore, removed pasted comment, added notnull to user_id in categories --- ...m251020_090412_create_table_categories.php | 2 +- frontend/.gitignore | 1 + frontend/vite.config.ts | 20 +++++++++---------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/api/migrations/m251020_090412_create_table_categories.php b/api/migrations/m251020_090412_create_table_categories.php index 9beb6bd..2c2389e 100644 --- a/api/migrations/m251020_090412_create_table_categories.php +++ b/api/migrations/m251020_090412_create_table_categories.php @@ -16,7 +16,7 @@ public function safeUp(): void { $this->createTable('{{%categories}}', [ 'id' => $this->primaryKey(), - 'user_id' => $this->integer()->notNull(), + 'user_id' => $this->integer(), 'name' => $this->string(255)->notNull(), 'created_at' => $this->dateTime()->notNull(), diff --git a/frontend/.gitignore b/frontend/.gitignore index a547bf3..b02a1ff 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +package-lock.json # Editor directories and files .vscode/* diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index ac9e0cf..13217ea 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -3,14 +3,14 @@ import react from '@vitejs/plugin-react-swc' // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], - server: { - proxy: { - '/api': { - target: 'http://localhost:8000', // 👈 your Yii server URL - changeOrigin: true, - secure: false, - }, - }, + plugins: [react()], + server: { + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + secure: false, + }, }, -}); + }, +}) From 703f75cda9d838580dc4a772853ab91b380755d8 Mon Sep 17 00:00:00 2001 From: Casper JB Date: Tue, 4 Nov 2025 14:12:38 +0000 Subject: [PATCH 05/74] added userid foreign keys to teams and boxes --- .../m251020_091751_create_table_boxes.php | 15 +++++++++++++++ .../m251020_092814_create_table_teams.php | 19 +++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/api/migrations/m251020_091751_create_table_boxes.php b/api/migrations/m251020_091751_create_table_boxes.php index f3cd252..7113b43 100644 --- a/api/migrations/m251020_091751_create_table_boxes.php +++ b/api/migrations/m251020_091751_create_table_boxes.php @@ -17,6 +17,7 @@ public function safeUp(): void $this->createTable('{{%boxes}}', [ 'id' => $this->primaryKey(), 'category_id' => $this->integer(), + 'user_id' => $this->integer(), 'name' => $this->string(255)->notNull(), ]); @@ -33,6 +34,19 @@ public function safeUp(): void 'SET NULL', 'CASCADE' ); + + /** + * user_id FK + */ + $this->addForeignKey( + 'fk-boxes-user_id', + '{{%boxes}}', + 'user_id', + '{{%users}}', + 'id', + 'CASCADE', + 'SET NULL' + ); } /** @@ -41,6 +55,7 @@ public function safeUp(): void public function safeDown(): void { $this->dropForeignKey('fk-boxes-category_id', '{{%boxes}}'); + $this->dropForeignKey('fk-boxes-user_id', '{{%boxes}}'); $this->dropTable('{{%boxes}}'); } } diff --git a/api/migrations/m251020_092814_create_table_teams.php b/api/migrations/m251020_092814_create_table_teams.php index 5dd45c9..b9d18b8 100644 --- a/api/migrations/m251020_092814_create_table_teams.php +++ b/api/migrations/m251020_092814_create_table_teams.php @@ -17,6 +17,7 @@ public function safeUp(): void $this->createTable('{{%teams}}', [ 'id' => $this->primaryKey(), 'category_id' => $this->integer(), + 'user_id' => $this->integer(), 'name' => $this->string(255)->notNull(), ]); @@ -26,13 +27,26 @@ public function safeUp(): void */ $this->addForeignKey( 'fk-teams-category_id', - '{{%boxes}}', + '{{%teams}}', 'category_id', '{{%categories}}', 'id', 'SET NULL', 'CASCADE' ); + + /** + * user_id FK + */ + $this->addForeignKey( + 'fk-teams-user_id', + '{{%teams}}', + 'user_id', + '{{%users}}', + 'id', + 'CASCADE', + 'SET NULL' + ); } /** @@ -40,7 +54,8 @@ public function safeUp(): void */ public function safeDown(): void { - $this->dropForeignKey('fk-teams-category_id', '{{%boxes}}'); + $this->dropForeignKey('fk-teams-category_id', '{{%teams}}'); + $this->dropForeignKey('fk-teams-user_id', '{{%teams}}'); $this->dropTable('{{%teams}}'); } } From 5b1c4574b968972ded958b7684a0cf97b6d96dd0 Mon Sep 17 00:00:00 2001 From: Casper JB Date: Wed, 5 Nov 2025 09:29:31 +0000 Subject: [PATCH 06/74] added a healthcheck and pokemon dropdown to test page for now --- frontend/src/pages/Test.tsx | 64 +++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 frontend/src/pages/Test.tsx diff --git a/frontend/src/pages/Test.tsx b/frontend/src/pages/Test.tsx new file mode 100644 index 0000000..d2ffe41 --- /dev/null +++ b/frontend/src/pages/Test.tsx @@ -0,0 +1,64 @@ +import { useEffect, useState } from 'react' +import Select from 'react-select' + +type PokemonSpecies = { + id: number + images_collection_id?: number + name: string + url: string +} + +export default function Test() { + const [health, setHealth] = useState('') + const [dropdownItems, setDropdownItems] = useState(['']) + const [selectedItem, setSelectedItem] = useState('') + + async function healthCheck() { + try { + const json = await (await fetch('http://127.0.0.1:8000/health/check')).json() + + setHealth(JSON.stringify(json)) + } catch (err) { + console.error(err) + } + } + + async function loadDropdownItems() { + try { + const json = await ( + await fetch(`http://127.0.0.1:8000/pokemon-species/index/?per-page=1000`) + ).json() + const items = json.items.map((item: PokemonSpecies) => { + return { + value: item.id, + label: item.name, + } + }) + setDropdownItems(items) + } catch (err) { + console.error(err) + } + } + + useEffect(() => { + loadDropdownItems().then() + }, []) + + return ( +
+
+ +
{health}
+
+
+ - setSelectedItem( - selected ?? { - value: 0, - label: 'Select A Pokemon', - } - ) - } - isSearchable={true} - /> - -
+ +
{health}
) } From 9363ba7bc3c59651e9d1bea20dc2b7e0954e53a6 Mon Sep 17 00:00:00 2001 From: Casper JB Date: Mon, 17 Nov 2025 18:02:35 +0000 Subject: [PATCH 54/74] added createNewTeam button --- frontend/src/pages/teams/Teams.tsx | 43 ++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/teams/Teams.tsx b/frontend/src/pages/teams/Teams.tsx index e37db17..17fb1df 100644 --- a/frontend/src/pages/teams/Teams.tsx +++ b/frontend/src/pages/teams/Teams.tsx @@ -4,11 +4,11 @@ import { useEffect, useState } from 'react' export default function Teams() { const BASE_URL = 'http://127.0.0.1:8000' const { categoryId } = useParams() - const [teams, setTeams] = useState<{ id: number; name: string; button: any }[]>([]) + const [teams, setTeams] = useState<{ button: any }[]>([]) async function loadTeams() { const json = await ( - await fetch(`${BASE_URL}/teams/category-view?categoryId=${categoryId}`) + await fetch(`${BASE_URL}/categories/teams-index?categoryId=${categoryId}`) ).json() const teams = json.map((item: any) => { return { @@ -16,7 +16,9 @@ export default function Teams() { name: item.name, button: ( ), } @@ -24,14 +26,45 @@ export default function Teams() { setTeams(teams) } + async function createNewTeam() { + const userId: number = 1 // (placeholder) todo: get userId of currently logged in user / localstorage + const userTeamCount: number = await ( + await fetch(`${BASE_URL}/categories/teams-count?categoryId=${categoryId}`) + ).json() + + await fetch(`${BASE_URL}/teams/create`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + category_id: categoryId, + user_id: userId, + name: `Untitled Team ${userTeamCount}`, + // created_at: '2007-09-24 00:00:00', + }), + }) + .then(res => res.json()) + .then(data => { + console.log(data) + }) + .catch(err => console.log(err)) + + await loadTeams() + } + useEffect(() => { loadTeams().then() - }) + }, []) return (

teams in category {categoryId}

-
{teams.map(team => team.button) ?? []}
+ +
+ {teams.map(category => category.button)} + +
) } From e5445963f9881d4b4761961c1b6ec079ced065d8 Mon Sep 17 00:00:00 2001 From: Casper JB Date: Mon, 17 Nov 2025 18:03:01 +0000 Subject: [PATCH 55/74] moved pokemon dropdown here and added rendering for pokemon image urls from the db --- frontend/src/pages/teams/Team.tsx | 129 +++++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/teams/Team.tsx b/frontend/src/pages/teams/Team.tsx index 8e2fdc1..f835af6 100644 --- a/frontend/src/pages/teams/Team.tsx +++ b/frontend/src/pages/teams/Team.tsx @@ -1,14 +1,135 @@ import { useParams } from 'react-router-dom' -import Test from '../Test.tsx' +import { useEffect, useState } from 'react' +import Select from 'react-select' + +const BASE_URL = 'http://127.0.0.1:8000' + +type PokemonInstance = { + id: number + box_id?: number + team_id?: number + pokemon_species_id: number + custom_name: string + format_id?: number +} + +type PokemonSpecies = { + id: number + image: number + name: string + url: string +} export default function Team() { - const { categoryId } = useParams() + const [pokemon, setPokemon] = useState<{ button: any }[]>([]) + const { teamId } = useParams() + + const [dropdownItems, setDropdownItems] = useState<{ value: number | null; label: string }[]>([ + { + value: null, + label: 'Select A Pokemon', + }, + ]) + const [selectedItem, setSelectedItem] = useState<{ value: number | null; label: string }>({ + value: null, + label: 'Select A Pokemon', + }) + + async function loadDropdownItems() { + try { + const json = await (await fetch(`${BASE_URL}/pokemon-species/index/?per-page=1000`)).json() + const items = json.items.map((item: PokemonSpecies) => { + return { + value: item.id, + label: item.name, + } + }) + setDropdownItems(items) + } catch (err) { + console.error(err) + } + } + + async function loadPokemon() { + const json = await (await fetch(`${BASE_URL}/teams/pokemon-index?teamId=${teamId}`)).json() + + const species: { image: string; name: string }[] = json.species.map((item: PokemonSpecies) => { + return { + image: item.image, + name: item.name, + } + }) + + const instance: { id: number }[] = json.instances.map((item: PokemonInstance) => { + return { + id: item.id, + } + }) + + const pokemon = instance.map((item, index) => { + const id = item.id + const imageUrl = species[index].image + const name = species[index].name + + const imageTag = {name} + return { + button: , + } + }) + + setPokemon(pokemon) + } + + async function addPokemon() { + if (!selectedItem.value) return + await fetch(`${BASE_URL}/pokemon-instances/create`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + // todo: add functionality for all of these columns that are currently using test values: + body: JSON.stringify({ + team_id: teamId, + box_id: null, // todo: check if this is a box or team page + pokemon_species_id: selectedItem.value, + format_id: null, + custom_name: 'unnamed', + // created_at: '2007-09-24 00:00:00', + }), + }) + loadPokemon().then() + } + + useEffect(() => { + loadDropdownItems().then() + }, []) + + useEffect(() => { + loadPokemon().then() + }, []) return (
-

category {categoryId}

- {/* just using the test page for now, todo: replace with proper page */} +