fixed some backend issues

This commit is contained in:
nvrl committed 2026-04-26 23:02:13 +02:00
1 parent 466d0a0f9f
commit 2b2b4cf48f
13 files changed
+6800 -62

No files matched your search

+7
View File
@@ -13,6 +13,13 @@ COPY requirements.txt .
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir -r requirements.txt
# Non-root user for celery workers/beat. UID matches typical host (1000) so
# bind-mounted volumes (./backend, ./legacy, media/) stay writable. Override
# by rebuilding with --build-arg APP_UID=<your uid> if your host differs.
ARG APP_UID=1000
ARG APP_GID=1000
RUN groupadd -g ${APP_GID} app && useradd -u ${APP_UID} -g app -m -s /bin/bash app
# Copy the rest of the backend code
COPY . .
+22 -11
View File
@@ -1,4 +1,5 @@
import json
from typing import Any
from django.conf import settings
from django.core.mail import send_mail
@@ -14,15 +15,26 @@ from api.serializers import (
JobSerializer,
SystemConfigSerializer,
)
from api.tasks import (
backup_db_task,
cleanup_duplicates_task,
seed_banners_task,
seed_legacy_task,
sync_flavors_task,
)
JOB_TASK_MAP = {
"sync_flavors": "api.sync_flavors",
"cleanup_duplicates": "api.cleanup_duplicates",
"backup_db": "api.backup_db",
"seed_legacy": "api.seed_legacy",
"seed_banners": "api.seed_banners",
# Maps Job.name → (celery task name for beat schedules, callable for direct dispatch).
# Direct callables let .delay() honor CELERY_TASK_ALWAYS_EAGER in dev.
JOB_TASKS: dict[str, tuple[str, Any]] = {
"sync_flavors": ("api.sync_flavors", sync_flavors_task),
"cleanup_duplicates": ("api.cleanup_duplicates", cleanup_duplicates_task),
"backup_db": ("api.backup_db", backup_db_task),
"seed_legacy": ("api.seed_legacy", seed_legacy_task),
"seed_banners": ("api.seed_banners", seed_banners_task),
}
JOB_TASK_MAP = {name: meta[0] for name, meta in JOB_TASKS.items()}
def _sync_periodic_task(job: Job) -> None:
"""Create/update a django_celery_beat PeriodicTask for this Job.
@@ -106,25 +118,24 @@ class AdminViewSet(viewsets.ViewSet):
@action(detail=True, methods=["post"])
def trigger_job(self, request: Request, pk=None) -> Response:
from celery import current_app
try:
job = Job.objects.get(pk=pk)
except Job.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
task_name = JOB_TASK_MAP.get(job.name)
if not task_name:
meta = JOB_TASKS.get(job.name)
if not meta:
return Response(
{"error": f"No celery task registered for {job.name}"},
status=status.HTTP_400_BAD_REQUEST,
)
task_name, task_fn = meta
job.status = "pending"
job.error_message = ""
job.save(update_fields=["status", "error_message"])
current_app.send_task(task_name)
task_fn.delay()
return Response({"status": "Job queued", "task": task_name})
@action(detail=True, methods=["patch"])
+2968
View File
File diff suppressed because it is too large. Load diff
+5
View File
@@ -5,6 +5,9 @@ services:
container_name: holy-redis
command: redis-server --appendonly yes --save 60 1
restart: unless-stopped
sysctls:
- net.core.somaxconn=512
- vm.overcommit_memory=1
networks:
- holy-net
volumes:
@@ -51,6 +54,7 @@ services:
context: ./backend
container_name: holy-worker
command: celery -A holy_backend worker -l info --concurrency=2
user: "app"
restart: unless-stopped
networks:
- holy-net
@@ -83,6 +87,7 @@ services:
context: ./backend
container_name: holy-beat
command: celery -A holy_backend beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler --pidfile /tmp/celerybeat.pid
user: "app"
restart: unless-stopped
networks:
- holy-net
+247
View File
@@ -42,6 +42,7 @@
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"jsdom": "^29.0.2",
"openapi-typescript": "^7.13.0",
"prettier": "^3.8.2",
"typescript": "~5.9.3",
"typescript-eslint": "^8.48.0",
@@ -2039,6 +2040,82 @@
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@redocly/ajv": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz",
"integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js-replace": "^1.0.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@redocly/ajv/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
"license": "MIT"
},
"node_modules/@redocly/config": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz",
"integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@redocly/openapi-core": {
"version": "1.34.12",
"resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.12.tgz",
"integrity": "sha512-b32XWsz6enN6K4bx8xWsqUaXTJR/DnYT3lL1CzDYzIYKw243NNlz6fexmr71q/U4HrEcMoJGBvwAfcxOb8ymQw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@redocly/ajv": "8.11.2",
"@redocly/config": "0.22.0",
"colorette": "1.4.0",
"https-proxy-agent": "7.0.6",
"js-levenshtein": "1.1.6",
"js-yaml": "4.1.1",
"minimatch": "5.1.9",
"pluralize": "8.0.0",
"yaml-ast-parser": "0.0.43"
},
"engines": {
"node": ">=18.17.0",
"npm": ">=9.5.0"
}
},
"node_modules/@redocly/openapi-core/node_modules/brace-expansion": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@redocly/openapi-core/node_modules/minimatch": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-rc.3",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
@@ -3163,6 +3240,16 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -3180,6 +3267,16 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -3452,6 +3549,13 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/change-case": {
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
"integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
"dev": true,
"license": "MIT"
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -3502,6 +3606,13 @@
"simple-swizzle": "^0.2.2"
}
},
"node_modules/colorette": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
"integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
"dev": true,
"license": "MIT"
},
"node_modules/colorthief": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/colorthief/-/colorthief-2.6.0.tgz",
@@ -4513,6 +4624,20 @@
"void-elements": "3.1.0"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/i18next": {
"version": "24.2.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz",
@@ -4619,6 +4744,19 @@
"node": ">=8"
}
},
"node_modules/index-to-position": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz",
"integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/iota-array": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz",
@@ -4689,6 +4827,16 @@
"dev": true,
"license": "ISC"
},
"node_modules/js-levenshtein": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
"integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -5067,6 +5215,58 @@
],
"license": "MIT"
},
"node_modules/openapi-typescript": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz",
"integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@redocly/openapi-core": "^1.34.6",
"ansi-colors": "^4.1.3",
"change-case": "^5.4.4",
"parse-json": "^8.3.0",
"supports-color": "^10.2.2",
"yargs-parser": "^21.1.1"
},
"bin": {
"openapi-typescript": "bin/cli.js"
},
"peerDependencies": {
"typescript": "^5.x"
}
},
"node_modules/openapi-typescript/node_modules/parse-json": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
"integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"index-to-position": "^1.1.0",
"type-fest": "^4.39.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/openapi-typescript/node_modules/supports-color": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
"integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -5234,6 +5434,16 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pluralize": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@@ -6055,6 +6265,19 @@
"node": ">= 0.8.0"
}
},
"node_modules/type-fest": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -6157,6 +6380,13 @@
"punycode": "^2.1.0"
}
},
"node_modules/uri-js-replace": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz",
"integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==",
"dev": true,
"license": "MIT"
},
"node_modules/vite": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
@@ -6446,6 +6676,23 @@
"dev": true,
"license": "ISC"
},
"node_modules/yaml-ast-parser": {
"version": "0.0.43",
"resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
"integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+5 -1
View File
@@ -12,7 +12,10 @@
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,json,css}\"",
"test": "vitest run",
"test:watch": "vitest",
"preview": "vite preview"
"preview": "vite preview",
"schema:export": "cd ../backend && ./venv/bin/python manage.py spectacular --file schema.yaml",
"openapi:gen": "openapi-typescript ../backend/schema.yaml -o src/api/schema.d.ts",
"openapi:sync": "npm run schema:export && npm run openapi:gen"
},
"dependencies": {
"@catppuccin/palette": "^1.7.1",
@@ -49,6 +52,7 @@
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"jsdom": "^29.0.2",
"openapi-typescript": "^7.13.0",
"prettier": "^3.8.2",
"typescript": "~5.9.3",
"typescript-eslint": "^8.48.0",
@@ -1,5 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import api from '../../lib/api';
import { useAuth } from '../../hooks/useAuth';
export interface CurrentUserLite {
username: string;
@@ -10,8 +11,9 @@ export interface CurrentUserLite {
* Reads /users/me/ for cases where we need a quick "is this me / am I admin"
* check outside of AuthContext. Disabled when not authenticated.
*/
export const useCurrentUserLite = () =>
useQuery({
export const useCurrentUserLite = () => {
const { user } = useAuth();
return useQuery({
queryKey: ['currentUserLite'],
queryFn: async () => {
const res = await api.get<CurrentUserLite>('users/me/', {
@@ -21,6 +23,7 @@ export const useCurrentUserLite = () =>
});
return res.data;
},
enabled: !!localStorage.getItem('access'),
enabled: !!user,
staleTime: 60_000,
});
};
+6 -3
View File
@@ -1,5 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import api from '../../lib/api';
import { useAuth } from '../../hooks/useAuth';
export interface CurrentUserFull {
id: number;
@@ -12,8 +13,9 @@ export interface CurrentUserFull {
unread_notifications_count?: number;
}
export const useMe = () =>
useQuery({
export const useMe = () => {
const { user } = useAuth();
return useQuery({
queryKey: ['me'],
queryFn: async () => {
const res = await api.get<CurrentUserFull>('users/me/', {
@@ -21,5 +23,6 @@ export const useMe = () =>
});
return res.data;
},
enabled: !!localStorage.getItem('access'),
enabled: !!user,
});
};
+3506
View File
File diff suppressed because it is too large. Load diff
+20 -39
View File
@@ -1,25 +1,25 @@
export interface Reply {
id: number;
user: string;
text: string;
created_at: string;
}
import type { components } from './schema';
export interface RatedItem {
id: number;
flavor: number;
flavor_name: string;
flavor_image: string | null;
category_name: string;
category_slug: string;
score: number;
comment: string | null;
created_at: string;
replies: Reply[];
is_legacy?: boolean;
is_available?: boolean;
}
export type Schemas = components['schemas'];
// Re-exported from generated OpenAPI schema.
// Regenerate with `npm run openapi:sync` after backend serializer changes.
export type Banner = Schemas['Banner'];
export type Category = Schemas['Category'];
export type Flavor = Schemas['Flavor'];
export type Notification = Schemas['Notification'];
export type Rating = Schemas['Rating'];
export type Reply = Schemas['Reply'];
export type Ticket = Schemas['Ticket'];
export type TicketMessage = Schemas['TicketMessage'];
export type User = Schemas['User'];
// `BannerConfig` was the legacy alias for the Banner schema; kept for callers.
export type BannerConfig = Banner;
// `RatedItem` was the legacy alias for Rating in dashboard payload.
export type RatedItem = Rating;
// Custom (non-DRF-serialized) shapes for endpoints without a serializer_class.
export interface MissingFlavor {
id: number;
name: string;
@@ -39,22 +39,3 @@ export interface DashboardData {
missing_flavors: MissingFlavor[];
my_ratings: RatedItem[];
}
export interface BannerConfig {
id: number;
name: string;
slug: string;
description: string;
is_active: boolean;
settings: Record<string, unknown> | null;
}
export interface NotificationItem {
id: number;
verb: string;
actor: string;
target: string | null;
url: string | null;
is_read: boolean;
created_at: string;
}
@@ -30,13 +30,12 @@ export const NotificationProvider = ({ children }: { children: ReactNode }) => {
// Refetch the auth user on the same 60s cadence so the unread count badge
// stays in sync with anything that mutated server-side outside this client.
useEffect(() => {
if (!user) return;
const interval = setInterval(() => {
if (localStorage.getItem('access')) {
refetchUser();
}
}, 60_000);
return () => clearInterval(interval);
}, [refetchUser]);
}, [refetchUser, user]);
const fetchNotifications = useCallback(async () => {
await refetch();
+3 -1
View File
@@ -35,6 +35,7 @@ import {
import { useNotificationsQuery } from '../api/queries/useNotifications';
import { useCreateReply } from '../api/mutations/useRatingMutations';
import { useTitle } from '../hooks/useTitle';
import { useAuth } from '../hooks/useAuth';
import RichText from '../components/RichText';
import { formatDate } from '../utils/date';
import { useTranslation } from 'react-i18next';
@@ -82,7 +83,8 @@ const CommunityFeed: React.FC<CommunityFeedProps> = ({ adminMode }) => {
const [replyInputs, setReplyInputs] = useState<Record<number, string>>({});
const [expandedReplies, setExpandedReplies] = useState<Record<number, boolean>>({});
const isAuthed = !!localStorage.getItem('access');
const { user } = useAuth();
const isAuthed = !!user;
const { data: feed, isLoading: feedLoading, error: feedError } = useCommunityFeed(page);
const { data: following = [] } = useFollowingList();
const { data: notificationsAll = [] } = useNotificationsQuery(isAuthed);
+3 -1
View File
@@ -20,6 +20,7 @@ import {
useTopFlavors,
} from '../api/queries/useMainPageQueries';
import { useTitle } from '../hooks/useTitle';
import { useAuth } from '../hooks/useAuth';
import RatingBadge from '../components/RatingBadge';
import StatusBadge from '../components/StatusBadge';
import {
@@ -86,7 +87,8 @@ const MainPage: React.FC<MainPageProps> = (_props) => {
const [activeIndex, setActiveIndex] = useState(0);
const query = new URLSearchParams(location.search).get('q') || '';
const isLoggedIn = !!localStorage.getItem('access');
const { user } = useAuth();
const isLoggedIn = !!user;
const { data: searchResults = [], isLoading: searchLoading } = useFlavorSearch(query);
const { data: topFlavorsAll = [], isLoading: topLoading } = useTopFlavors(null);