-
Notifications
You must be signed in to change notification settings - Fork 10
Feature add character search ability #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
698df5e
77a276d
a463e73
76642a3
7fb9a2f
8354111
1c3befe
7ec5dac
15d0929
16c527d
8cdd22d
793c6d7
504b5c9
4ac4f16
57a5133
3348545
1aaf3df
3f6a4d3
909ec1e
2efa63d
5d23a6e
b218159
f275135
a667537
f8079e1
8cb653c
719054e
f516af8
7790e4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # pd2-tools Copilot Instructions | ||
|
|
||
| ## Big picture | ||
| - Monorepo with two apps: **api/** (Node/Express + Postgres/Redis) and **web/** (Vite + React + Mantine). API serves data consumed by the web client at /api/v1. | ||
| - API startup: api/src/index.ts wires Redis + DB shutdown; api/src/server.ts builds the Express app and mounts routes at /api/${API_VERSION}. | ||
| - Background work is intentionally separate from the HTTP process: api/src/jobs.ts starts cron jobs (character scraper, online player tracker, leaderboard updater). Production runs multiple API instances, so avoid mixing jobs into the server entry. | ||
|
|
||
| ## Data flow & storage | ||
| - Primary data lives in Postgres; schemas are auto-created in code on startup: | ||
| - Character/leaderboard schema: api/src/database/postgres/index.ts | ||
| - Economy schema: api/src/database/postgres/economy.ts | ||
| - Redis is optional and used as a read-through cache. If Redis is unavailable, calls fall back to DB (see api/src/utils/cache.ts). | ||
| - GET routes commonly use auto-caching with deterministic cache keys; the `skills` query param is URL-encoded JSON and normalized for cache keys (api/src/middleware/auto-cache.ts). | ||
|
|
||
| ## API conventions | ||
| - Routes are grouped in api/src/routes and assembled in api/src/routes/index.ts. | ||
| - Query validation is explicit; `validateSeason` enforces positive integers and attaches `seasonNumber` to the request (api/src/middleware/validation.ts). | ||
| - Responses use `{ error: { message } }` on failures; not-found handled by middleware (api/src/middleware/error-handler.ts). | ||
|
|
||
| ## Frontend conventions | ||
| - API calls are centralized in web/src/api with a shared fetch wrapper (web/src/api/client.ts) and endpoint constants (web/src/config/api.ts). | ||
| - React Query is the default data-fetching layer with a 5-minute staleTime (web/src/App.tsx). | ||
| - Mantine is the UI system; global theme and routing live in web/src/App.tsx. | ||
|
|
||
| ## External integrations | ||
| - PD2 public API is polled by background jobs (api/src/jobs/*) and by character scraper logic with rate limiting and profanity filtering. | ||
| - Economy/leaderboard data depends on scheduled jobs; avoid changing cron timing without understanding load constraints in api/src/jobs/character-scraper.ts. | ||
|
|
||
| ## Dev workflows | ||
| - API: npm run dev (ts-node), npm run build, npm start, npm run jobs, npm test. | ||
| - Web: npm run dev, npm run build, npm run preview. | ||
| - Both apps rely on .env files (see README.md and .env.example in each app). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,7 @@ router.get( | |
| skills, | ||
| mercTypes, | ||
| mercItems, | ||
| query, | ||
| season, | ||
| } = req.query; | ||
|
|
||
|
|
@@ -87,6 +88,9 @@ router.get( | |
| if (mercItems) { | ||
| filter.requiredMercItems = (mercItems as string).split(","); | ||
| } | ||
| if (query && (query as string).trim().length >= 3) { | ||
| filter.searchQuery = (query as string).trim(); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Were there database changes you forgot to commit? I don't see any mention of |
||
| } | ||
| if (season) { | ||
| filter.season = parseInt(season as string, 10); | ||
| } else { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -237,7 +237,10 @@ export default function ClassBar({ | |
| updateFilters, | ||
| }: BuildsComponentProps) { | ||
| const isMobile = useMediaQuery("(max-width: 767px)"); | ||
| const [searchInput, setSearchInput] = useState(filters.searchQuery); | ||
| const [searchInput, setSearchInput] = useState(filters.filterSearchQuery); | ||
| const [characterSearchInput, setCharacterSearchInput] = useState( | ||
| filters.searchQuery | ||
| ); | ||
| const [settingsOpened, setSettingsOpened] = useState(false); | ||
| const [accountQueueOpened, setAccountQueueOpened] = useState(false); | ||
| const searchTimeout = useRef<NodeJS.Timeout>(); | ||
|
|
@@ -263,7 +266,7 @@ export default function ClassBar({ | |
| } | ||
|
|
||
| searchTimeout.current = setTimeout(() => { | ||
| updateFilters({ searchQuery: value }); | ||
| updateFilters({ filterSearchQuery: value }); | ||
| }, 300); | ||
| }; | ||
|
|
||
|
|
@@ -272,11 +275,29 @@ export default function ClassBar({ | |
| if (searchTimeout.current) { | ||
| clearTimeout(searchTimeout.current); | ||
| } | ||
| updateFilters({ filterSearchQuery: "" }); | ||
| }; | ||
|
|
||
| const handleCharacterSearchChange = ( | ||
| e: React.ChangeEvent<HTMLInputElement> | ||
| ) => { | ||
| const value = e.target.value; | ||
| setCharacterSearchInput(value); | ||
| if (value.length >= 3 || value.length === 0) { | ||
| updateFilters({ searchQuery: value }); | ||
| } else { | ||
| updateFilters({ searchQuery: "" }); | ||
| } | ||
| }; | ||
|
|
||
| const handleCharacterSearchClear = () => { | ||
| setCharacterSearchInput(""); | ||
| updateFilters({ searchQuery: "" }); | ||
| }; | ||
|
|
||
| const handleResetFilters = () => { | ||
| setSearchInput(""); | ||
| setCharacterSearchInput(""); | ||
| if (searchTimeout.current) { | ||
| clearTimeout(searchTimeout.current); | ||
| } | ||
|
|
@@ -287,6 +308,7 @@ export default function ClassBar({ | |
| mercTypeFilter: [], | ||
| mercItemFilter: [], | ||
| searchQuery: "", | ||
| filterSearchQuery: "", | ||
| }); | ||
| }; | ||
|
|
||
|
|
@@ -398,15 +420,34 @@ export default function ClassBar({ | |
| }} | ||
| > | ||
| <Flex align="center" justify="space-between" h="100%"> | ||
| {!isMobile && ( | ||
| <Text> | ||
| Found{" "} | ||
| <Text span fw={700}> | ||
| {data.breakdown.total?.toLocaleString()} | ||
| </Text>{" "} | ||
| characters | ||
| </Text> | ||
| )} | ||
| <Flex align="center" gap="md"> | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would look nicer if the search bar was floated to the far right, so that its directly to the left of the settings button
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'd also want to debounce this to avoid api requests as they're actively typing characters |
||
| <TextInput | ||
| placeholder="Search characters..." | ||
| rightSection={ | ||
| characterSearchInput ? ( | ||
| <ActionIcon | ||
| variant="transparent" | ||
| color="gray" | ||
| onClick={handleCharacterSearchClear} | ||
| > | ||
| <IconX size={16} /> | ||
| </ActionIcon> | ||
| ) : null | ||
| } | ||
| value={characterSearchInput} | ||
| onChange={handleCharacterSearchChange} | ||
| style={{ width: isMobile ? "100%" : "260px" }} | ||
| /> | ||
| {!isMobile && ( | ||
| <Text> | ||
| Found{" "} | ||
| <Text span fw={700}> | ||
| {data.breakdown.total?.toLocaleString()} | ||
| </Text>{" "} | ||
| characters | ||
| </Text> | ||
| )} | ||
| </Flex> | ||
| <Flex gap="md" style={{ marginLeft: isMobile ? "0" : "auto" }}> | ||
| <Button | ||
| variant="outline" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you remove this from the PR?