├── backend ├── __init__.py ├── app_state │ ├── __init__.py │ ├── rbq.py │ ├── db.py │ └── redis.py ├── core │ ├── configs │ │ ├── __init__.py │ │ └── coverpage.jpg │ └── database │ │ ├── __init__.py │ │ ├── models │ │ ├── base.py │ │ ├── notification.py │ │ ├── degree_audit.py │ │ ├── __init__.py │ │ ├── media.py │ │ └── common_enums.py │ │ └── manager.py ├── modules │ ├── auth │ │ ├── __init__.py │ │ ├── dependencies.py │ │ ├── schemas.py │ │ └── cruds.py │ ├── search │ │ ├── utils.py │ │ └── __init__.py │ ├── bot │ │ ├── utils │ │ │ ├── __init__.py │ │ │ ├── enums.py │ │ │ ├── permissions.py │ │ │ └── google_bucket.py │ │ ├── filters │ │ │ ├── __init__.py │ │ │ └── deeplink.py │ │ ├── locales │ │ │ ├── en │ │ │ │ └── LC_MESSAGES │ │ │ │ │ └── messages.mo │ │ │ ├── kz │ │ │ │ └── LC_MESSAGES │ │ │ │ │ └── messages.mo │ │ │ └── ru │ │ │ │ └── LC_MESSAGES │ │ │ │ └── messages.mo │ │ ├── routes │ │ │ ├── __init__.py │ │ │ └── user │ │ │ │ └── private │ │ │ │ ├── messages │ │ │ │ ├── start.py │ │ │ │ └── start_deeplink.py │ │ │ │ ├── callback │ │ │ │ └── confirmation.py │ │ │ │ └── __init__.py │ │ ├── hints_command.py │ │ ├── README.md │ │ ├── keyboards │ │ │ └── callback_factory.py │ │ ├── middlewares │ │ │ ├── public_url.py │ │ │ ├── redis.py │ │ │ ├── bucket_client.py │ │ │ ├── db_session.py │ │ │ ├── __init__.py │ │ │ └── i18n.py │ │ ├── cruds.py │ │ └── bot.py │ ├── google_bucket │ │ ├── cruds.py │ │ └── __init__.py │ ├── campuscurrent │ │ ├── __init__.py │ │ ├── events │ │ │ ├── __init__.py │ │ │ └── dependencies.py │ │ ├── communities │ │ │ └── __init__.py │ │ ├── profile │ │ │ ├── __init__.py │ │ │ └── profile.py │ │ └── base.py │ ├── announcements │ │ ├── __init__.py │ │ ├── router.py │ │ └── service.py │ ├── courses │ │ ├── planner │ │ │ ├── __init__.py │ │ │ └── dependencies.py │ │ ├── registrar │ │ │ ├── parsers │ │ │ │ └── __init__.py │ │ │ ├── clients │ │ │ │ └── __init__.py │ │ │ ├── tests │ │ │ │ ├── test_priority_sync_worker.py │ │ │ │ ├── test_registrar_parser.py │ │ │ │ └── test_priority_parser.py │ │ │ └── priority_sync_worker.py │ │ ├── degree_audit │ │ │ ├── requirements │ │ │ │ └── additional_tables │ │ │ │ │ ├── Degree audit requirments for all majors - Shortcuts.csv │ │ │ │ │ └── Degree audit requirments for all majors - Soc_hum electives.csv │ │ │ └── dependencies.py │ │ ├── courses │ │ │ ├── errors.py │ │ │ └── base.py │ │ └── statistics │ │ │ └── schemas.py │ ├── notion │ │ ├── __init__.py │ │ ├── utils.py │ │ └── consts.py │ ├── notification │ │ ├── schemas.py │ │ ├── notification.py │ │ └── tasks.py │ ├── sgotinish │ │ ├── base.py │ │ └── messages │ │ │ └── dependencies.py │ └── __init__.py ├── common │ ├── utils │ │ └── enums.py │ └── schemas.py ├── start.sh ├── migrations │ └── script.py.mako ├── Dockerfile ├── main.py └── lifespan.py ├── infra ├── pgadmin │ ├── pgpass │ └── servers.json ├── rabbitmq │ └── rabbitmq.conf ├── grafana │ └── provisioning │ │ ├── dashboards │ │ └── default.yaml │ │ ├── alerting │ │ ├── alerting.yaml │ │ └── contact-points.yaml.tpl │ │ └── datasources │ │ └── datasources.yml ├── prometheus │ ├── grafana_alert_rules.yml │ ├── alertmanager.yml.tpl │ └── prometheus.yml ├── build.docker-compose.yaml ├── scripts │ ├── start-alertmanager.sh │ └── start-grafana.sh └── nginx │ └── vpn-index.html ├── ansible ├── roles │ ├── backend │ │ └── meta │ │ │ └── main.yml │ ├── frontend │ │ └── meta │ │ │ └── main.yml │ ├── secrets │ │ └── meta │ │ │ └── main.yml │ ├── infra_services │ │ └── meta │ │ │ └── main.yml │ ├── container_analysis │ │ └── meta │ │ │ └── main.yml │ ├── post_tasks │ │ └── tasks │ │ │ └── main.yml │ └── repo_sync │ │ └── tasks │ │ └── main.yml └── inventory.yml ├── frontend ├── src │ ├── vite-env.d.ts │ ├── assets │ │ ├── favicon.png │ │ ├── images │ │ │ ├── miniapp.webp │ │ │ ├── nu_logo.png │ │ │ ├── teams │ │ │ │ ├── adil.jpg │ │ │ │ ├── alan.jpg │ │ │ │ ├── ulan.jpg │ │ │ │ ├── aisana.jpg │ │ │ │ ├── yelnur.jpg │ │ │ │ └── bakhtiyar.jpg │ │ │ ├── event_pics │ │ │ │ ├── 1.webp │ │ │ │ ├── 2.webp │ │ │ │ ├── 3.webp │ │ │ │ ├── 4.webp │ │ │ │ └── 5.webp │ │ │ ├── google_form.png │ │ │ ├── categories │ │ │ │ ├── all.png │ │ │ │ ├── food.png │ │ │ │ ├── books.png │ │ │ │ ├── others.png │ │ │ │ ├── sports.png │ │ │ │ ├── clothing.png │ │ │ │ ├── furniture.png │ │ │ │ ├── transport.png │ │ │ │ ├── appliances.png │ │ │ │ └── electronics.png │ │ │ ├── hero_assets │ │ │ │ ├── 1.webp │ │ │ │ ├── 2.webp │ │ │ │ ├── 3.webp │ │ │ │ ├── 4.webp │ │ │ │ └── 5.webp │ │ │ ├── miniapp-resized.webp │ │ │ ├── welcome-nu-space.jpg │ │ │ └── nu-space-presentation.jpg │ │ ├── icons │ │ │ ├── apple-icon-180.png │ │ │ ├── apple-splash-1136-640.jpg │ │ │ ├── apple-splash-1334-750.jpg │ │ │ ├── apple-splash-1792-828.jpg │ │ │ ├── apple-splash-640-1136.jpg │ │ │ ├── apple-splash-750-1334.jpg │ │ │ ├── apple-splash-828-1792.jpg │ │ │ ├── apple-splash-1125-2436.jpg │ │ │ ├── apple-splash-1170-2532.jpg │ │ │ ├── apple-splash-1179-2556.jpg │ │ │ ├── apple-splash-1206-2622.jpg │ │ │ ├── apple-splash-1242-2208.jpg │ │ │ ├── apple-splash-1242-2688.jpg │ │ │ ├── apple-splash-1260-2736.jpg │ │ │ ├── apple-splash-1284-2778.jpg │ │ │ ├── apple-splash-1290-2796.jpg │ │ │ ├── apple-splash-1320-2868.jpg │ │ │ ├── apple-splash-1488-2266.jpg │ │ │ ├── apple-splash-1536-2048.jpg │ │ │ ├── apple-splash-1620-2160.jpg │ │ │ ├── apple-splash-1640-2360.jpg │ │ │ ├── apple-splash-1668-2224.jpg │ │ │ ├── apple-splash-1668-2388.jpg │ │ │ ├── apple-splash-2048-1536.jpg │ │ │ ├── apple-splash-2048-2732.jpg │ │ │ ├── apple-splash-2160-1620.jpg │ │ │ ├── apple-splash-2208-1242.jpg │ │ │ ├── apple-splash-2224-1668.jpg │ │ │ ├── apple-splash-2266-1488.jpg │ │ │ ├── apple-splash-2360-1640.jpg │ │ │ ├── apple-splash-2388-1668.jpg │ │ │ ├── apple-splash-2436-1125.jpg │ │ │ ├── apple-splash-2532-1170.jpg │ │ │ ├── apple-splash-2556-1179.jpg │ │ │ ├── apple-splash-2622-1206.jpg │ │ │ ├── apple-splash-2688-1242.jpg │ │ │ ├── apple-splash-2732-2048.jpg │ │ │ ├── apple-splash-2736-1260.jpg │ │ │ ├── apple-splash-2778-1284.jpg │ │ │ ├── apple-splash-2796-1290.jpg │ │ │ ├── apple-splash-2868-1320.jpg │ │ │ ├── manifest-icon-192.maskable.png │ │ │ └── manifest-icon-512.maskable.png │ │ ├── svg │ │ │ ├── telegram-connected.svg │ │ │ └── Vector.svg │ │ └── manifest.json │ ├── pages │ │ ├── profile.tsx │ │ ├── about.tsx │ │ ├── apps │ │ │ ├── emergency.tsx │ │ │ └── contacts.tsx │ │ └── apps-layout.tsx │ ├── utils │ │ ├── utils.ts │ │ ├── query-client.ts │ │ ├── image-utils.tsx │ │ ├── search-params.ts │ │ └── animationVariants.ts │ ├── components │ │ ├── CampusLayout.tsx │ │ ├── atoms │ │ │ ├── spinner.tsx │ │ │ ├── label.tsx │ │ │ ├── textarea.tsx │ │ │ ├── progress.tsx │ │ │ ├── slider-button.tsx │ │ │ ├── toggle.tsx │ │ │ ├── input.tsx │ │ │ ├── sonner.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── switch.tsx │ │ │ ├── badge.tsx │ │ │ ├── popover.tsx │ │ │ └── avatar.tsx │ │ ├── organisms │ │ │ ├── media │ │ │ │ └── index.ts │ │ │ ├── about │ │ │ │ ├── about-us-section.tsx │ │ │ │ ├── about-header.tsx │ │ │ │ ├── mission-section.tsx │ │ │ │ ├── feature-card.tsx │ │ │ │ └── report-card.tsx │ │ │ ├── filter-container.tsx │ │ │ ├── animations │ │ │ │ ├── AnimatedCard.tsx │ │ │ │ └── AnimatedFormField.tsx │ │ │ ├── category-grid.tsx │ │ │ ├── category-slider.tsx │ │ │ └── admin │ │ │ │ └── stat-card.tsx │ │ ├── molecules │ │ │ ├── hoc │ │ │ │ └── with-suspense.tsx │ │ │ ├── buttons │ │ │ │ ├── message-button.tsx │ │ │ │ ├── submit-button.tsx │ │ │ │ ├── donate-button.tsx │ │ │ │ ├── login-button.tsx │ │ │ │ ├── channel-button.tsx │ │ │ │ └── report-button.tsx │ │ │ ├── QueryBoundary.tsx │ │ │ ├── BackButton.tsx │ │ │ ├── general-section.tsx │ │ │ ├── telegram-status.tsx │ │ │ ├── auth-required-alert.tsx │ │ │ ├── theme-toggle.tsx │ │ │ ├── DeleteConfirmation.tsx │ │ │ ├── combined-search.tsx │ │ │ └── login-modal.tsx │ │ ├── ui │ │ │ └── footer.tsx │ │ ├── templates │ │ │ └── about-template.tsx │ │ ├── layout │ │ │ ├── ProtectedRoute.tsx │ │ │ └── PublicRoute.tsx │ │ └── animations │ │ │ └── AnimatedCard.tsx │ ├── types │ │ ├── images.d.ts │ │ └── search.ts │ ├── features │ │ ├── sgotinish │ │ │ ├── utils │ │ │ │ ├── roleMapping.ts │ │ │ │ └── date.ts │ │ │ └── components │ │ │ │ └── CreateAppealButton.tsx │ │ ├── announcements │ │ │ └── api │ │ │ │ └── useTelegramPosts.ts │ │ ├── courses │ │ │ ├── api │ │ │ │ └── hooks │ │ │ │ │ ├── useGradeTerms.ts │ │ │ │ │ └── usePreSearchGrades.ts │ │ │ └── components │ │ │ │ ├── forms │ │ │ │ └── NumericInput.tsx │ │ │ │ ├── TrendIndicator.tsx │ │ │ │ └── live-gpa │ │ │ │ └── SummaryCards.tsx │ │ ├── events │ │ │ ├── hooks │ │ │ │ ├── useEvent.ts │ │ │ │ ├── useVirtualEvents.ts │ │ │ │ ├── useEvents.ts │ │ │ │ └── useInfiniteEvents.ts │ │ │ └── utils │ │ │ │ ├── calendar.ts │ │ │ │ └── eventFormatters.ts │ │ ├── media │ │ │ ├── types │ │ │ │ ├── media.ts │ │ │ │ └── types.ts │ │ │ ├── utils │ │ │ │ ├── get-signed-urls.ts │ │ │ │ └── upload-media.ts │ │ │ └── index.ts │ │ └── communities │ │ │ ├── hooks │ │ │ ├── use-user-communities.ts │ │ │ ├── useVirtualCommunities.ts │ │ │ ├── use-search-communities.ts │ │ │ ├── use-edit-community.ts │ │ │ ├── useInfiniteCommunities.ts │ │ │ ├── use-community.ts │ │ │ ├── useDeleteCommunity.ts │ │ │ └── use-communities.ts │ │ │ └── api │ │ │ └── hooks │ │ │ └── usePreSearchCommunities.ts │ ├── hooks │ │ ├── useIsMacSafari.ts │ │ ├── useFormAnimations.ts │ │ ├── useDebounce.ts │ │ └── useGlobalSecondTicker.ts │ ├── layouts │ │ ├── PublicLayout.tsx │ │ └── LoggedInLayout.tsx │ ├── data │ │ ├── features.ts │ │ └── kp │ │ │ └── product.tsx │ └── app │ │ └── main.tsx ├── Dockerfile_static_builder ├── tsconfig.node.json ├── capacitor.config.ts ├── tsconfig.json └── Dockerfile_vite ├── terraform ├── tfscheme.png ├── backend.tfbackend ├── backend.tf ├── providers.tf ├── .terraform.lock.hcl └── envs │ ├── production.tfvars │ └── staging.tfvars ├── .gitmodules ├── docs └── TODO.md ├── .pre-commit-config.yaml ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore └── LICENSE /backend/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app_state/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/core/configs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/core/database/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/auth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/search/utils.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/auth/dependencies.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/bot/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/google_bucket/cruds.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/search/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/campuscurrent/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/google_bucket/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/campuscurrent/events/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/campuscurrent/communities/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/campuscurrent/profile/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /infra/pgadmin/pgpass: -------------------------------------------------------------------------------- 1 | postgres:5432:postgres:postgres:123 2 | -------------------------------------------------------------------------------- /ansible/roles/backend/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: [] 3 | 4 | -------------------------------------------------------------------------------- /ansible/roles/frontend/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: [] 3 | 4 | -------------------------------------------------------------------------------- /ansible/roles/secrets/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: [] 3 | 4 | -------------------------------------------------------------------------------- /ansible/roles/infra_services/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: [] 3 | 4 | -------------------------------------------------------------------------------- /backend/modules/announcements/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .router import router 3 | -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | 4 | -------------------------------------------------------------------------------- /ansible/roles/container_analysis/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: [] 3 | 4 | -------------------------------------------------------------------------------- /backend/modules/bot/filters/__init__.py: -------------------------------------------------------------------------------- 1 | from .deeplink import EncodedDeepLinkFilter 2 | -------------------------------------------------------------------------------- /infra/rabbitmq/rabbitmq.conf: -------------------------------------------------------------------------------- 1 | log.console = true 2 | log.console.level = info 3 | log.file = false -------------------------------------------------------------------------------- /terraform/tfscheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/terraform/tfscheme.png -------------------------------------------------------------------------------- /backend/modules/courses/planner/__init__.py: -------------------------------------------------------------------------------- 1 | from .planner import router 2 | 3 | __all__ = ["router"] 4 | 5 | -------------------------------------------------------------------------------- /frontend/src/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/favicon.png -------------------------------------------------------------------------------- /backend/core/configs/coverpage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/backend/core/configs/coverpage.jpg -------------------------------------------------------------------------------- /frontend/src/pages/profile.tsx: -------------------------------------------------------------------------------- 1 | export { default } from "@/features/profile/ProfilePage"; 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /terraform/backend.tfbackend: -------------------------------------------------------------------------------- 1 | bucket = "nuspace-terraform-state" 2 | credentials = "./creds/staging.json" 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/images/miniapp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/miniapp.webp -------------------------------------------------------------------------------- /frontend/src/assets/images/nu_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/nu_logo.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "prbot"] 2 | path = backend/modules/bot/prbot 3 | url = https://github.com/mciiee/nuspace-prtelebot.git 4 | -------------------------------------------------------------------------------- /frontend/src/assets/images/teams/adil.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/teams/adil.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/teams/alan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/teams/alan.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/teams/ulan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/teams/ulan.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-icon-180.png -------------------------------------------------------------------------------- /frontend/src/assets/images/event_pics/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/event_pics/1.webp -------------------------------------------------------------------------------- /frontend/src/assets/images/event_pics/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/event_pics/2.webp -------------------------------------------------------------------------------- /frontend/src/assets/images/event_pics/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/event_pics/3.webp -------------------------------------------------------------------------------- /frontend/src/assets/images/event_pics/4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/event_pics/4.webp -------------------------------------------------------------------------------- /frontend/src/assets/images/event_pics/5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/event_pics/5.webp -------------------------------------------------------------------------------- /frontend/src/assets/images/google_form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/google_form.png -------------------------------------------------------------------------------- /frontend/src/assets/images/teams/aisana.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/teams/aisana.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/teams/yelnur.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/teams/yelnur.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/categories/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/categories/all.png -------------------------------------------------------------------------------- /frontend/src/assets/images/categories/food.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/categories/food.png -------------------------------------------------------------------------------- /frontend/src/assets/images/hero_assets/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/hero_assets/1.webp -------------------------------------------------------------------------------- /frontend/src/assets/images/hero_assets/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/hero_assets/2.webp -------------------------------------------------------------------------------- /frontend/src/assets/images/hero_assets/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/hero_assets/3.webp -------------------------------------------------------------------------------- /frontend/src/assets/images/hero_assets/4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/hero_assets/4.webp -------------------------------------------------------------------------------- /frontend/src/assets/images/hero_assets/5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/hero_assets/5.webp -------------------------------------------------------------------------------- /frontend/src/assets/images/teams/bakhtiyar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/teams/bakhtiyar.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/categories/books.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/categories/books.png -------------------------------------------------------------------------------- /frontend/src/assets/images/categories/others.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/categories/others.png -------------------------------------------------------------------------------- /frontend/src/assets/images/categories/sports.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/categories/sports.png -------------------------------------------------------------------------------- /frontend/src/assets/images/miniapp-resized.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/miniapp-resized.webp -------------------------------------------------------------------------------- /frontend/src/assets/images/welcome-nu-space.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/welcome-nu-space.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1136-640.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1136-640.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1334-750.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1334-750.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1792-828.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1792-828.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-640-1136.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-640-1136.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-750-1334.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-750-1334.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-828-1792.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-828-1792.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/categories/clothing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/categories/clothing.png -------------------------------------------------------------------------------- /frontend/src/assets/images/categories/furniture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/categories/furniture.png -------------------------------------------------------------------------------- /frontend/src/assets/images/categories/transport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/categories/transport.png -------------------------------------------------------------------------------- /backend/modules/bot/locales/en/LC_MESSAGES/messages.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/backend/modules/bot/locales/en/LC_MESSAGES/messages.mo -------------------------------------------------------------------------------- /backend/modules/bot/locales/kz/LC_MESSAGES/messages.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/backend/modules/bot/locales/kz/LC_MESSAGES/messages.mo -------------------------------------------------------------------------------- /backend/modules/bot/locales/ru/LC_MESSAGES/messages.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/backend/modules/bot/locales/ru/LC_MESSAGES/messages.mo -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1125-2436.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1125-2436.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1170-2532.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1170-2532.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1179-2556.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1179-2556.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1206-2622.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1206-2622.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1242-2208.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1242-2208.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1242-2688.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1242-2688.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1260-2736.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1260-2736.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1284-2778.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1284-2778.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1290-2796.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1290-2796.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1320-2868.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1320-2868.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1488-2266.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1488-2266.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1536-2048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1536-2048.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1620-2160.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1620-2160.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1640-2360.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1640-2360.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1668-2224.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1668-2224.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-1668-2388.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-1668-2388.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2048-1536.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2048-1536.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2048-2732.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2048-2732.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2160-1620.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2160-1620.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2208-1242.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2208-1242.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2224-1668.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2224-1668.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2266-1488.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2266-1488.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2360-1640.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2360-1640.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2388-1668.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2388-1668.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2436-1125.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2436-1125.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2532-1170.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2532-1170.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2556-1179.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2556-1179.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2622-1206.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2622-1206.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2688-1242.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2688-1242.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2732-2048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2732-2048.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2736-1260.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2736-1260.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2778-1284.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2778-1284.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2796-1290.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2796-1290.jpg -------------------------------------------------------------------------------- /frontend/src/assets/icons/apple-splash-2868-1320.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/apple-splash-2868-1320.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/categories/appliances.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/categories/appliances.png -------------------------------------------------------------------------------- /frontend/src/assets/images/categories/electronics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/categories/electronics.png -------------------------------------------------------------------------------- /frontend/src/assets/images/nu-space-presentation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/images/nu-space-presentation.jpg -------------------------------------------------------------------------------- /backend/modules/bot/utils/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class NotificationEnum(str, Enum): 5 | ENABLE = "enable" 6 | DISABLE = "disable" 7 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/manifest-icon-192.maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/manifest-icon-192.maskable.png -------------------------------------------------------------------------------- /frontend/src/assets/icons/manifest-icon-512.maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulanpy/nuspace/HEAD/frontend/src/assets/icons/manifest-icon-512.maskable.png -------------------------------------------------------------------------------- /backend/core/database/models/base.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base 2 | 3 | Base: DeclarativeMeta = declarative_base() 4 | -------------------------------------------------------------------------------- /terraform/backend.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "gcs" { 3 | # bucket and credentials configured via -backend-config during init 4 | prefix = "infra" 5 | } 6 | } -------------------------------------------------------------------------------- /ansible/roles/post_tasks/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Post-deployment tasks 3 | # DOCKER_IMAGE_TAG environment variable is no longer needed since we use 'latest' tag directly 4 | 5 | -------------------------------------------------------------------------------- /backend/modules/courses/registrar/parsers/__init__.py: -------------------------------------------------------------------------------- 1 | """Parsers for transforming registrar payloads.""" 2 | 3 | from .registrar_parser import parse_schedule # noqa: F401 4 | 5 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | # TODOs 2 | ## Current 3 | - Telegram bot for PR: 4 | Assigned: @mciiee 5 | 6 | - Backend architecture rewrite. 7 | Assigned: NO 8 | 9 | ## On hold 10 | -------------------------------------------------------------------------------- /frontend/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | import { AboutTemplate } from "@/components/templates/about-template"; 2 | 3 | export default function AboutPage() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /infra/grafana/provisioning/dashboards/default.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: Default 5 | type: file 6 | options: 7 | path: /var/lib/grafana/dashboards 8 | -------------------------------------------------------------------------------- /backend/common/utils/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ResourceAction(str, Enum): 5 | CREATE = "create" 6 | READ = "read" 7 | UPDATE = "update" 8 | DELETE = "delete" 9 | -------------------------------------------------------------------------------- /backend/modules/campuscurrent/profile/profile.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | router = APIRouter(tags=["Profile"]) 4 | 5 | 6 | @router.get("/profile") 7 | async def get_profile(): 8 | return 9 | -------------------------------------------------------------------------------- /infra/grafana/provisioning/alerting/alerting.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: "default" 5 | type: file 6 | options: 7 | path: /etc/grafana/provisioning/alerting/alerts.yaml 8 | -------------------------------------------------------------------------------- /terraform/providers.tf: -------------------------------------------------------------------------------- 1 | # providers.tf 2 | 3 | provider "google" { 4 | project = var.project_id 5 | credentials = file(var.credentials_file) 6 | region = var.region 7 | zone = var.zone 8 | } 9 | -------------------------------------------------------------------------------- /backend/modules/courses/degree_audit/requirements/additional_tables/Degree audit requirments for all majors - Shortcuts.csv: -------------------------------------------------------------------------------- 1 | LANG 2 | GER 3 | ARB 4 | KOR 5 | POL 6 | DUT 7 | CHN 8 | FRE 9 | PER 10 | SPA -------------------------------------------------------------------------------- /ansible/inventory.yml: -------------------------------------------------------------------------------- 1 | --- 2 | all: 3 | children: 4 | webservers: 5 | hosts: 6 | # This will be populated by the GitHub Actions workflow 7 | # with the actual host from secrets.ANSIBLE_HOST 8 | -------------------------------------------------------------------------------- /frontend/src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /backend/modules/courses/registrar/clients/__init__.py: -------------------------------------------------------------------------------- 1 | """HTTP clients for NU registrar endpoints.""" 2 | 3 | from .public_course_catalog import PublicCourseCatalogClient # noqa: F401 4 | from .registrar_client import RegistrarClient # noqa: F401 5 | 6 | -------------------------------------------------------------------------------- /backend/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ "$IS_DEBUG" = "false" ]; then 3 | gunicorn -w $(( $(nproc) * 2 + 1 )) -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 backend.main:app; 4 | else 5 | uvicorn backend.main:app --host 0.0.0.0 --port 8000 --reload; 6 | fi 7 | -------------------------------------------------------------------------------- /frontend/Dockerfile_static_builder: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine 2 | WORKDIR /app 3 | 4 | # Copy package files 5 | COPY frontend/package*.json ./ 6 | 7 | # Install dependencies 8 | RUN npm install 9 | 10 | # Copy source code (build is done in the container command) 11 | COPY frontend . -------------------------------------------------------------------------------- /backend/modules/courses/courses/errors.py: -------------------------------------------------------------------------------- 1 | class SemesterResolutionError(Exception): 2 | """Raised when we cannot resolve the current registrar semester.""" 3 | 4 | 5 | class CourseLookupError(Exception): 6 | """Raised when a course cannot be found in registrar search.""" 7 | 8 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /backend/modules/bot/routes/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram import Dispatcher 2 | 3 | from backend.modules.bot.routes.user.private import setup_private_routers 4 | 5 | def include_routers(dp: Dispatcher) -> None: 6 | private_router = setup_private_routers() 7 | dp.include_router(private_router) 8 | -------------------------------------------------------------------------------- /frontend/src/components/CampusLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | 3 | export function CampusLayout() { 4 | return ( 5 |
6 |
7 | 8 |
9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/components/atoms/spinner.tsx: -------------------------------------------------------------------------------- 1 | export const Spinner = () => { 2 | return ( 3 |
4 |
5 |
6 | ); 7 | }; 8 | -------------------------------------------------------------------------------- /infra/grafana/provisioning/datasources/datasources.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: Prometheus 5 | type: prometheus 6 | url: http://prometheus:9090/prometheus/ 7 | access: proxy 8 | 9 | - name: Loki 10 | type: loki 11 | url: http://loki:3100 12 | access: proxy 13 | -------------------------------------------------------------------------------- /ansible/roles/repo_sync/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Pull latest changes from git repository 3 | ansible.builtin.git: 4 | repo: "https://github.com/ulanpy/nuspace.git" 5 | dest: "/home/{{ ansible_user }}/nuspace" 6 | version: "{{ github_ref_name | default('main') }}" 7 | force: yes 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/src/types/images.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | const src: string; 3 | export default src; 4 | } 5 | 6 | declare module "*.png" { 7 | const src: string; 8 | export default src; 9 | } 10 | 11 | declare module "*.jpg" { 12 | const src: string; 13 | export default src; 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/utils/query-client.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from "@tanstack/react-query"; 2 | 3 | export const queryClient = new QueryClient({ 4 | defaultOptions: { 5 | queries: { 6 | staleTime: 1000 * 60 * 10, 7 | retry: false, 8 | refetchOnWindowFocus: false, 9 | }, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /backend/modules/notion/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Notion integration module. 3 | 4 | Provides services and background workers to sync ticket data with Notion. 5 | """ 6 | 7 | from .service import NotionService 8 | from .schemas import NotionTicketMessage 9 | 10 | __all__ = ["NotionService", "NotionTicketMessage"] 11 | 12 | 13 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: v0.11.5 # актуальную версию можно посмотреть на GitHub 4 | hooks: 5 | - id: ruff 6 | args: [--fix] 7 | 8 | - repo: https://github.com/psf/black 9 | rev: 24.3.0 10 | hooks: 11 | - id: black 12 | -------------------------------------------------------------------------------- /frontend/src/components/organisms/media/index.ts: -------------------------------------------------------------------------------- 1 | // Core components 2 | export { MediaPreview, MediaPreviewDefaults } from './MediaPreview'; 3 | export type { MediaPreviewProps } from './MediaPreview'; 4 | 5 | // Re-export commonly used types for convenience 6 | export type { MediaItem, MediaAction } from '@/features/media/types/media'; 7 | -------------------------------------------------------------------------------- /backend/modules/courses/degree_audit/dependencies.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from functools import lru_cache 4 | 5 | from backend.modules.courses.degree_audit.service import DegreeAuditService 6 | 7 | 8 | @lru_cache(maxsize=1) 9 | def get_degree_audit_service() -> DegreeAuditService: 10 | return DegreeAuditService() 11 | 12 | -------------------------------------------------------------------------------- /frontend/capacitor.config.ts: -------------------------------------------------------------------------------- 1 | import type { CapacitorConfig } from '@capacitor/cli'; 2 | 3 | const config: CapacitorConfig = { 4 | appId: 'kz.nuspace.mobile', 5 | appName: 'nuspace', 6 | webDir: 'dist', 7 | server: { 8 | url: process.env.CAPACITOR_SERVER ?? 'https://nuspace.kz', 9 | cleartext: true 10 | }, 11 | }; 12 | 13 | export default config; 14 | -------------------------------------------------------------------------------- /infra/prometheus/grafana_alert_rules.yml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: GrafanaDown 3 | rules: 4 | - alert: GrafanaDown 5 | expr: up{job="grafana"} == 0 6 | for: 40m 7 | labels: 8 | severity: critical 9 | annotations: 10 | summary: "Графана не доступна" 11 | description: "@sagyzdop Графана не доступна более 40 минут" 12 | -------------------------------------------------------------------------------- /infra/pgadmin/servers.json: -------------------------------------------------------------------------------- 1 | { 2 | "Servers": { 3 | "1": { 4 | "Name": "PostgreSQL", 5 | "Group": "Servers", 6 | "Host": "postgres", 7 | "Port": 5432, 8 | "MaintenanceDB": "postgres", 9 | "Username": "postgres", 10 | "PassFile": "/var/lib/pgadmin/pgpass", 11 | "SSLMode": "prefer", 12 | "ConnectNow": true 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/features/sgotinish/utils/roleMapping.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Maps backend role names to display names 3 | */ 4 | export const mapRoleToDisplayName = (role: string): string => { 5 | switch (role) { 6 | case "boss": 7 | return "head"; 8 | case "capo": 9 | return "executive"; 10 | case "soldier": 11 | return "member"; 12 | default: 13 | return role; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/hoc/with-suspense.tsx: -------------------------------------------------------------------------------- 1 | import { Spinner } from "@/components/atoms/spinner"; 2 | import { ComponentType, Suspense } from "react"; 3 | 4 | export const withSuspense = 5 |

(Component: ComponentType

) => 6 | (props: P) => { 7 | return ( 8 | }> 9 | 10 | 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /backend/modules/bot/hints_command.py: -------------------------------------------------------------------------------- 1 | from aiogram import Bot 2 | from aiogram.types import BotCommand, BotCommandScopeAllPrivateChats 3 | 4 | 5 | async def set_commands(bot: Bot): 6 | start = BotCommand(command="start", description="🟢 Start") 7 | language = BotCommand(command="language", description="🟢 Change language") 8 | await bot.set_my_commands(commands=[start, language], scope=BotCommandScopeAllPrivateChats()) 9 | -------------------------------------------------------------------------------- /backend/modules/bot/README.md: -------------------------------------------------------------------------------- 1 | ### Telegram Bot Localization binary compilation 2 | 3 | ```bash 4 | msgfmt backend/routes/bot/locales/ru/LC_MESSAGES/messages.po -o backend/routes/bot/locales/ru/LC_MESSAGES/messages.mo 5 | msgfmt backend/routes/bot/locales/en/LC_MESSAGES/messages.po -o backend/routes/bot/locales/en/LC_MESSAGES/messages.mo 6 | msgfmt backend/routes/bot/locales/kz/LC_MESSAGES/messages.po -o backend/routes/bot/locales/kz/LC_MESSAGES/messages.mo 7 | ``` -------------------------------------------------------------------------------- /frontend/src/components/organisms/about/about-us-section.tsx: -------------------------------------------------------------------------------- 1 | import { ReportCard } from "@/components/organisms/about/report-card"; 2 | import { TeamCard } from "@/components/organisms/about/team-card"; 3 | 4 | export function AboutUsSection() { 5 | return ( 6 |

7 |
8 | 9 | 10 |
11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /infra/grafana/provisioning/alerting/contact-points.yaml.tpl: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | contactPoints: 4 | - orgId: 1 5 | name: Telegram 6 | receivers: 7 | - uid: telegram 8 | type: telegram 9 | disableResolveMessage: false 10 | allowedit: true 11 | settings: 12 | bottoken: '${TELEGRAM_BOT_TOKEN}' 13 | chatid: '${TELEGRAM_CHAT_ID}' 14 | message: | 15 | {{ template "default.message" . }} -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Bug Description** 11 | Clear description of the issue. 12 | 13 | **Steps to Reproduce** 14 | 1. 15 | 16 | **Expected Behavior** 17 | What should happen instead. 18 | 19 | **Screenshots (optional)** 20 | Add if helpful. 21 | 22 | **Environment (optional)** 23 | - OS: 24 | - Browser/App: 25 | -------------------------------------------------------------------------------- /frontend/src/components/ui/footer.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import { ROUTES } from "@/data/routes"; 3 | 4 | interface FooterProps { 5 | note: string; 6 | } 7 | 8 | export function Footer({ note }: FooterProps) { 9 | return ( 10 |
11 | 12 | {note} 13 | 14 |
15 | ); 16 | } -------------------------------------------------------------------------------- /infra/prometheus/alertmanager.yml.tpl: -------------------------------------------------------------------------------- 1 | global: 2 | resolve_timeout: 5m 3 | 4 | route: 5 | receiver: 'telegram' 6 | 7 | receivers: 8 | - name: 'telegram' 9 | telegram_configs: 10 | - bot_token: '${TELEGRAM_BOT_TOKEN}' 11 | chat_id: ${TELEGRAM_CHAT_ID} 12 | message: | 13 | [{{ .Status | toUpper }}] {{ .CommonAnnotations.summary }} 14 | {{ range .Alerts }} 15 | Description: {{ .Annotations.description }} 16 | {{ end }} -------------------------------------------------------------------------------- /frontend/src/features/announcements/api/useTelegramPosts.ts: -------------------------------------------------------------------------------- 1 | 2 | import { useQuery } from "@tanstack/react-query"; 3 | import { apiCall } from "@/utils/api"; 4 | 5 | export const useTelegramPosts = () => { 6 | return useQuery({ 7 | queryKey: ["announcements", "telegram"], 8 | queryFn: async () => { 9 | const res = await apiCall<{ latest_post_id: number }>("/announcements/telegram"); 10 | return res.latest_post_id; 11 | } 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /backend/modules/bot/keyboards/callback_factory.py: -------------------------------------------------------------------------------- 1 | from aiogram.filters.callback_data import CallbackData 2 | from backend.modules.bot.utils.enums import NotificationEnum 3 | 4 | 5 | class ConfirmTelegramUser(CallbackData, prefix="confirm"): 6 | sub: str 7 | number: int 8 | confirmation_number: int 9 | 10 | 11 | class Languages(CallbackData, prefix="language"): 12 | language: str 13 | 14 | 15 | class NotificationAction(CallbackData, prefix="notif"): 16 | action: NotificationEnum 17 | -------------------------------------------------------------------------------- /frontend/src/hooks/useIsMacSafari.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export function useIsMacSafari() { 4 | const [isMacSafari, setIsMacSafari] = useState(false); 5 | 6 | useEffect(() => { 7 | if (typeof navigator === "undefined") return; 8 | const ua = navigator.userAgent; 9 | const detected = 10 | /Macintosh/.test(ua) && /Safari/.test(ua) && !/(Chrome|Chromium|Edg)/.test(ua); 11 | setIsMacSafari(detected); 12 | }, []); 13 | 14 | return isMacSafari; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/utils/image-utils.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Gets a placeholder image URL with cache busting 3 | * @param width Width of the image 4 | * @param height Height of the image 5 | * @returns A placeholder image URL 6 | */ 7 | export function getPlaceholderImage(width = 200, height = 200): string { 8 | // Add a timestamp to prevent caching 9 | const timestamp = new Date().getTime(); 10 | 11 | // Universal placeholder for all products 12 | return `https://placehold.co/${width}x${height}/EEE/31343C?text=No+Image&_t=${timestamp}`; 13 | } 14 | -------------------------------------------------------------------------------- /infra/build.docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | fastapi: 3 | image: kamikadze24/fastapi:${TAG} 4 | 5 | build: 6 | context: .. 7 | dockerfile: backend/Dockerfile 8 | args: 9 | IS_DEBUG: ${IS_DEBUG:-true} # Default to 'true' if not set 10 | 11 | frontend-builder: 12 | image: kamikadze24/frontendbuilder:${TAG} 13 | build: 14 | context: .. 15 | dockerfile: frontend/Dockerfile_static_builder 16 | command: sh -c "npm run build" # Run build command on container start 17 | restart: "no" 18 | -------------------------------------------------------------------------------- /backend/modules/announcements/router.py: -------------------------------------------------------------------------------- 1 | 2 | from fastapi import APIRouter, HTTPException 3 | from backend.modules.announcements.service import get_latest_telegram_post_id 4 | 5 | router = APIRouter( 6 | prefix="/announcements", 7 | tags=["announcements"], 8 | ) 9 | 10 | @router.get("/telegram") 11 | async def get_announcements_from_telegram(): 12 | """ 13 | Get latest announcements from the public Telegram channel. 14 | """ 15 | latest_id = await get_latest_telegram_post_id() 16 | return {"latest_post_id": latest_id} 17 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/buttons/message-button.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/atoms/button"; 2 | import { MessageSquare } from "lucide-react"; 3 | 4 | export function MessageButton() { 5 | return ( 6 |
7 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/features/courses/api/hooks/useGradeTerms.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { apiCall } from "@/utils/api"; 3 | import { GradeTermsResponse } from "../../types"; 4 | 5 | export const useGradeTerms = () => { 6 | const { data, isLoading } = useQuery({ 7 | queryKey: ["grade-terms"], 8 | queryFn: async ({ signal }) => { 9 | return apiCall("/grades/terms", { signal }); 10 | }, 11 | }); 12 | 13 | return { 14 | terms: data?.terms ?? [], 15 | isLoading, 16 | }; 17 | }; 18 | 19 | -------------------------------------------------------------------------------- /frontend/src/types/search.ts: -------------------------------------------------------------------------------- 1 | export interface PreSearchedItem { 2 | id: number | string; 3 | name: string; 4 | } 5 | 6 | export type SearchInputProps = { 7 | inputValue: string; 8 | setInputValue: (value: string) => void; 9 | preSearchedItems: PreSearchedItem[] | null; 10 | handleSearch: (inputValue: string) => void; 11 | setKeyword: (keyword: string) => void; 12 | // Optional, used in some contexts (e.g., products) to reset a secondary filter 13 | setSelectedCondition?: (condition: string) => void; 14 | placeholder?: string; 15 | }; 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/src/features/events/hooks/useEvent.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { campuscurrentAPI } from "../api/eventsApi"; 3 | import { useParams } from "react-router-dom"; 4 | 5 | export const useEvent = () => { 6 | const { id } = useParams<{ id: string }>(); 7 | const { 8 | data: event, 9 | isPending, 10 | isLoading, 11 | isError, 12 | } = useQuery({ 13 | ...campuscurrentAPI.getEventQueryOptions(id || ""), 14 | enabled: !!id, 15 | }); 16 | return { event: event || null, isPending, isLoading, isError }; 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/src/features/media/types/media.ts: -------------------------------------------------------------------------------- 1 | // src/features/media/types/media.ts 2 | 3 | export interface MediaItem { 4 | id?: number | string; 5 | url: string; 6 | name?: string; 7 | size?: number; 8 | isMain?: boolean; 9 | type?: 'image' | 'video' | 'document'; 10 | } 11 | 12 | export interface MediaAction { 13 | id: string; 14 | label: string; 15 | icon: React.ComponentType<{ className?: string }>; 16 | onClick: (index: number, item: MediaItem) => void; 17 | variant?: 'default' | 'destructive'; 18 | showInHover?: boolean; 19 | showInDropdown?: boolean; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /frontend/src/components/templates/about-template.tsx: -------------------------------------------------------------------------------- 1 | import { AboutHeader } from "@/components/organisms/about/about-header"; 2 | import { MessionSection } from "@/components/organisms/about/mission-section"; 3 | import { AboutUsSection } from "@/components/organisms/about/about-us-section"; 4 | 5 | export function AboutTemplate() { 6 | return ( 7 |
8 |
9 | 10 | 11 | 12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /backend/app_state/rbq.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from faststream.rabbit import RabbitBroker 3 | 4 | from backend.modules.notification import tasks 5 | from backend.modules.notion import tasks as notion_tasks # noqa: F401 (register subscribers) 6 | 7 | 8 | async def setup_rbq(app: FastAPI): 9 | broker: RabbitBroker = tasks.broker 10 | await broker.connect() 11 | await broker.start() 12 | 13 | app.state.broker = broker 14 | 15 | 16 | async def cleanup_rbq(app: FastAPI): 17 | broker: RabbitBroker = tasks.broker 18 | await broker.stop() 19 | app.state.broker = None 20 | -------------------------------------------------------------------------------- /backend/modules/bot/middlewares/public_url.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Awaitable, Callable 2 | 3 | from aiogram import BaseMiddleware 4 | from aiogram.types import TelegramObject 5 | 6 | 7 | class UrlMiddleware(BaseMiddleware): 8 | def __init__(self, url: str) -> None: 9 | self.url = url 10 | 11 | async def __call__( 12 | self, 13 | handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]], 14 | event: TelegramObject, 15 | data: dict[str, Any], 16 | ) -> Any: 17 | data["public_url"] = self.url 18 | return await handler(event, data) 19 | -------------------------------------------------------------------------------- /frontend/src/features/communities/hooks/use-user-communities.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { campuscurrentAPI } from "../api/communitiesApi"; 3 | 4 | export const useUserCommunities = (userSub: string | null | undefined) => { 5 | const { data, isLoading, isError } = useQuery({ 6 | ...campuscurrentAPI.getUserCommunitiesQueryOptions(userSub || ""), 7 | enabled: !!userSub, 8 | }); 9 | 10 | return { 11 | communities: (data as any)?.communities || [], 12 | isLoading, 13 | isError, 14 | totalCommunities: (data as any)?.total_pages || 0, 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /backend/modules/auth/schemas.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | from pydantic import BaseModel, EmailStr 4 | 5 | from backend.core.database.models.user import UserRole, UserScope 6 | 7 | 8 | class UserSchema(BaseModel): 9 | email: EmailStr 10 | role: UserRole 11 | scope: UserScope 12 | name: str 13 | surname: str 14 | picture: str 15 | sub: str 16 | 17 | 18 | class Sub(BaseModel): 19 | sub: str 20 | 21 | 22 | class CurrentUserResponse(BaseModel): 23 | user: Dict[str, Any] # This will store user token data 24 | tg_id: int | None = None # Indicates if user exists in the database 25 | -------------------------------------------------------------------------------- /frontend/src/hooks/useFormAnimations.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export function useFormAnimations() { 4 | const [focusedField, setFocusedField] = useState(null); 5 | 6 | const handleFieldFocus = (fieldName: string) => { 7 | setFocusedField(fieldName); 8 | }; 9 | 10 | const handleFieldBlur = () => { 11 | setFocusedField(null); 12 | }; 13 | 14 | const isFieldFocused = (fieldName: string) => { 15 | return focusedField === fieldName; 16 | }; 17 | 18 | return { 19 | focusedField, 20 | handleFieldFocus, 21 | handleFieldBlur, 22 | isFieldFocused, 23 | }; 24 | } -------------------------------------------------------------------------------- /frontend/src/features/events/hooks/useVirtualEvents.ts: -------------------------------------------------------------------------------- 1 | import { useInfiniteScroll } from '@/hooks/useInfiniteScroll'; 2 | import { Event } from '@/features/events/types'; 3 | import * as Routes from '@/data/routes'; 4 | 5 | export function useVirtualEvents(keyword: string = "") { 6 | return useInfiniteScroll({ 7 | queryKey: ["campusCurrent", "events"], 8 | apiEndpoint: `/${Routes.EVENTS}`, 9 | size: 12, 10 | keyword, 11 | additionalParams: {}, 12 | estimateSize: () => 250, // Estimate each event card to be 250px tall 13 | overscan: 4, // Only render 4 items outside viewport 14 | }); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/layouts/PublicLayout.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Outlet } from "react-router-dom"; 4 | import { IconThemeToggle } from "@/components/atoms/icon-theme-toggle"; 5 | 6 | export function PublicLayout() { 7 | return ( 8 |
9 |
10 | 11 |
12 |
13 | 14 |
15 |
16 | ); 17 | } 18 | 19 | export default PublicLayout; 20 | -------------------------------------------------------------------------------- /backend/modules/bot/middlewares/redis.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Awaitable, Callable 2 | 3 | from aiogram import BaseMiddleware 4 | from aiogram.types import TelegramObject 5 | from redis.asyncio import Redis 6 | 7 | 8 | class RedisMiddleware(BaseMiddleware): 9 | def __init__(self, redis: Redis) -> None: 10 | self.redis = redis 11 | 12 | async def __call__( 13 | self, 14 | handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]], 15 | event: TelegramObject, 16 | data: dict[str, Any], 17 | ) -> Any: 18 | data["redis"] = self.redis 19 | return await handler(event, data) 20 | -------------------------------------------------------------------------------- /backend/modules/bot/routes/user/private/messages/start.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from aiogram import Router 4 | from aiogram.filters import CommandStart 5 | from aiogram.types import Message 6 | from redis.asyncio import Redis 7 | 8 | from backend.modules.bot.keyboards.kb import kb_languages, kb_url 9 | 10 | router = Router() 11 | 12 | 13 | @router.message(CommandStart(deep_link=False)) 14 | async def user_start(m: Message, public_url: str, _: Callable[[str], str], redis: Redis): 15 | await m.answer( 16 | _("Добро пожаловать в NUspace, перейди по ссылке ниже!"), 17 | reply_markup=kb_url(url=public_url), 18 | ) 19 | -------------------------------------------------------------------------------- /infra/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | 4 | scrape_configs: 5 | - job_name: grafana 6 | honor_timestamps: true 7 | scrape_interval: 15s 8 | scrape_timeout: 10s 9 | metrics_path: /metrics 10 | scheme: http 11 | follow_redirects: true 12 | static_configs: 13 | - targets: 14 | - grafana:3000 15 | metric_relabel_configs: 16 | - source_labels: [__name__] 17 | action: keep 18 | regex: "(up)" 19 | 20 | alerting: 21 | alertmanagers: 22 | - static_configs: 23 | - targets: ["alertmanager:9093"] 24 | 25 | rule_files: 26 | - "grafana_alert_rules.yml" 27 | -------------------------------------------------------------------------------- /frontend/src/features/communities/hooks/useVirtualCommunities.ts: -------------------------------------------------------------------------------- 1 | import { useInfiniteScroll } from '@/hooks/useInfiniteScroll'; 2 | import { Community } from '@/features/communities/types'; 3 | import * as Routes from '@/data/routes'; 4 | 5 | export function useVirtualCommunities(keyword: string = "") { 6 | return useInfiniteScroll({ 7 | queryKey: ["campusCurrent", "communities"], 8 | apiEndpoint: `/${Routes.COMMUNITIES}`, 9 | size: 12, 10 | keyword, 11 | additionalParams: {}, 12 | estimateSize: () => 200, // Estimate each community card to be 200px tall 13 | overscan: 4, // Only render 4 items outside viewport 14 | }); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/features/sgotinish/components/CreateAppealButton.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/atoms/button"; 2 | import { FileText } from "lucide-react"; 3 | 4 | interface CreateAppealButtonProps { 5 | onClick: () => void; 6 | } 7 | 8 | export function CreateAppealButton({ onClick }: CreateAppealButtonProps) { 9 | return ( 10 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /backend/app_state/db.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from backend.core.configs.config import config 4 | from backend.core.database.manager import AsyncDatabaseManager 5 | 6 | 7 | async def setup_db(app: FastAPI): 8 | app.state.db_manager = AsyncDatabaseManager() 9 | # Avoid implicit schema creation in production – rely on Alembic migrations instead 10 | 11 | # === When modifying tables, comment this out! === 12 | if config.IS_DEBUG: 13 | await app.state.db_manager.create_all_tables() 14 | 15 | 16 | async def cleanup_db(app: FastAPI): 17 | db_manager = getattr(app.state, "db_manager", None) 18 | if db_manager: 19 | await db_manager.async_engine.dispose() 20 | -------------------------------------------------------------------------------- /frontend/src/components/organisms/about/about-header.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "@/context/ThemeProviderContext"; 2 | 3 | export const AboutHeader = () => { 4 | const { theme } = useTheme(); 5 | const isDark = theme === "dark"; 6 | return ( 7 |
8 |

9 | About Nuspace 10 |

11 |

16 | SuperApp for Nazarbayev University students 17 |

18 |
19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /frontend/src/hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | 3 | export function useDebounce(value: T, delay: number): T { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | const isFirstRender = useRef(true); 6 | 7 | useEffect(() => { 8 | if (isFirstRender.current) { 9 | isFirstRender.current = false; 10 | setDebouncedValue(value); 11 | return; 12 | } 13 | 14 | const timer = window.setTimeout(() => { 15 | setDebouncedValue(value); 16 | }, delay); 17 | 18 | return () => { 19 | window.clearTimeout(timer); 20 | }; 21 | }, [value, delay]); 22 | 23 | return debouncedValue; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /frontend/src/pages/apps/emergency.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ContactsInfoSection } from "@/components/organisms/contacts-info-section"; 4 | 5 | export default function ContactsPage() { 6 | return ( 7 |
8 |
9 |

Contacts & Essential Services

10 |

11 | Save these contacts. In an emergency, call campus security or local services immediately. 12 |

13 |
14 | 15 |
16 | ); 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/features/sgotinish/utils/date.ts: -------------------------------------------------------------------------------- 1 | const ensureUtcString = (value: string): string => { 2 | // If the string already has timezone info (Z or +/-HH:MM), keep it 3 | if (/([zZ]|[+-]\d{2}:?\d{2})$/.test(value)) { 4 | return value; 5 | } 6 | 7 | // Otherwise assume backend sent UTC without suffix, so append Z 8 | return `${value}Z`; 9 | }; 10 | 11 | export const toLocalDate = (value: Date | string): Date => { 12 | if (value instanceof Date) { 13 | return value; 14 | } 15 | 16 | return new Date(ensureUtcString(value)); 17 | }; 18 | 19 | export const toLocalISOString = (value: Date | string): string => { 20 | return toLocalDate(value).toLocaleString(); 21 | }; 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /backend/app_state/redis.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from redis.asyncio import ConnectionPool, Redis 3 | 4 | from backend.core.configs.config import config 5 | 6 | 7 | async def setup_redis(app: FastAPI): 8 | redis_pool = ConnectionPool.from_url( 9 | config.REDIS_URL, 10 | max_connections=50, 11 | socket_connect_timeout=5, 12 | socket_timeout=10, 13 | health_check_interval=30, 14 | retry_on_timeout=True, 15 | decode_responses=True, 16 | ) 17 | app.state.redis = Redis(connection_pool=redis_pool) 18 | 19 | 20 | async def cleanup_redis(app: FastAPI): 21 | if redis := getattr(app.state, "redis", None): 22 | await redis.aclose() 23 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/telegram-connected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /backend/modules/bot/middlewares/bucket_client.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Awaitable, Callable 2 | 3 | from aiogram import BaseMiddleware 4 | from aiogram.types import TelegramObject 5 | from google.cloud import storage 6 | 7 | 8 | class BucketClientMiddleware(BaseMiddleware): 9 | def __init__(self, storage_client: storage.Client) -> None: 10 | self.storage_client = storage_client 11 | 12 | async def __call__( 13 | self, 14 | handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]], 15 | event: TelegramObject, 16 | data: dict[str, Any], 17 | ) -> Any: 18 | data["storage_client"] = self.storage_client 19 | return await handler(event, data) 20 | -------------------------------------------------------------------------------- /frontend/src/features/events/utils/calendar.ts: -------------------------------------------------------------------------------- 1 | import { Event } from "../../types/types"; 2 | 3 | export const addToGoogleCalendar = (event: Event) => { 4 | const eventDate = new Date(event.start_datetime); 5 | const endDate = new Date(event.end_datetime); 6 | 7 | const googleCalendarUrl = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent( 8 | event.name, 9 | )}&dates=${eventDate 10 | .toISOString() 11 | .replace(/-|:|\.\d+/g, "")}/${endDate 12 | .toISOString() 13 | .replace(/-|:|\.\d+/g, "")}&details=${encodeURIComponent( 14 | event.description, 15 | )}&location=${encodeURIComponent(event.place)}`; 16 | 17 | window.open(googleCalendarUrl, "_blank"); 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/components/organisms/filter-container.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface FilterContainerProps { 4 | children: React.ReactNode; 5 | className?: string; 6 | title?: string; 7 | } 8 | 9 | export function FilterContainer({ children, className = "", title }: FilterContainerProps) { 10 | return ( 11 |
20 | {title && ( 21 |

22 | {title} 23 |

24 | )} 25 | {children} 26 |
27 | ); 28 | } -------------------------------------------------------------------------------- /backend/modules/bot/utils/permissions.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import ChatPermissions 2 | 3 | no_permissions = ChatPermissions( 4 | can_send_messages=False, 5 | can_send_audios=False, 6 | can_send_documents=False, 7 | can_send_photos=False, 8 | can_send_videos=False, 9 | can_send_video_notes=False, 10 | can_send_voice_notes=False, 11 | can_send_polls=False, 12 | can_invite_users=False, 13 | ) 14 | 15 | all_permissions = ChatPermissions( 16 | can_send_messages=True, 17 | can_send_audios=True, 18 | can_send_documents=True, 19 | can_send_photos=True, 20 | can_send_videos=True, 21 | can_send_video_notes=True, 22 | can_send_voice_notes=True, 23 | can_send_polls=True, 24 | can_invite_users=True, 25 | ) 26 | -------------------------------------------------------------------------------- /backend/modules/bot/filters/deeplink.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from aiogram.filters import BaseFilter, CommandObject 4 | from aiogram.types import Message 5 | from aiogram.utils.payload import decode_payload 6 | 7 | 8 | class EncodedDeepLinkFilter(BaseFilter): 9 | def __init__(self, prefix: Optional[str] = None): 10 | self.prefix = prefix 11 | 12 | async def __call__(self, message: Message, command: CommandObject) -> bool: 13 | args = command.args 14 | payload: str = decode_payload(args) 15 | 16 | if not payload: 17 | return False 18 | 19 | if self.prefix: 20 | if payload.startswith(self.prefix): 21 | return True 22 | return False 23 | return False 24 | -------------------------------------------------------------------------------- /frontend/src/data/features.ts: -------------------------------------------------------------------------------- 1 | import { MdEvent, MdRestaurantMenu } from "react-icons/md"; 2 | import { ROUTES } from "./routes"; 3 | export const features = [ 4 | { 5 | title: "Events", 6 | description: 7 | "Information about holidays, meetings and events that take place on the territory of the University. Students will be able to find activities that are interesting to them.", 8 | icon: MdEvent, 9 | link: ROUTES.EVENTS.ROOT, 10 | }, 11 | { 12 | title: "Dorm Eats", 13 | description: 14 | "Daily menu in the university canteen. What dishes are available, what dishes are being prepared - all this students have the opportunity to find out in advance.", 15 | icon: MdRestaurantMenu, 16 | link: ROUTES.DORM_EATS, 17 | }, 18 | ]; 19 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/QueryBoundary.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from "react"; 2 | 3 | export type QueryBoundaryProps = { 4 | data: TData | null | undefined; 5 | isLoading?: boolean; 6 | isError?: boolean; 7 | loadingFallback?: ReactNode; 8 | errorFallback?: ReactNode; 9 | children: (data: TData) => ReactNode; 10 | }; 11 | 12 | export function QueryBoundary({ 13 | data, 14 | isLoading = false, 15 | isError = false, 16 | loadingFallback = null, 17 | errorFallback = null, 18 | children, 19 | }: QueryBoundaryProps) { 20 | if (isLoading) { 21 | return <>{loadingFallback}; 22 | } 23 | 24 | if (isError || data == null) { 25 | return <>{errorFallback}; 26 | } 27 | 28 | return <>{children(data)}; 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /frontend/src/features/media/utils/get-signed-urls.ts: -------------------------------------------------------------------------------- 1 | import { UploadMediaOptions } from "../types/types"; 2 | import { SignedUrlRequest, SignedUrlResponse } from "../types/types"; 3 | import { mediaApi } from "../api/mediaApi"; 4 | 5 | export const getSignedUrls = async ( 6 | entityId: number, 7 | files: File[], 8 | options: Omit, 9 | ): Promise => { 10 | const requests: SignedUrlRequest[] = files.map((file, idx) => ({ 11 | entity_type: options.entity_type, 12 | entity_id: entityId, 13 | media_format: options.mediaFormat, 14 | media_order: (options.startOrder || 0) + idx, 15 | mime_type: file.type, 16 | content_type: file.type, 17 | })); 18 | 19 | return await mediaApi.getSignedUrls(requests); 20 | }; -------------------------------------------------------------------------------- /backend/migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from typing import Sequence, Union 9 | 10 | from alembic import op 11 | import sqlalchemy as sa 12 | ${imports if imports else ""} 13 | 14 | # revision identifiers, used by Alembic. 15 | revision: str = ${repr(up_revision)} 16 | down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} 17 | branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} 18 | depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} 19 | 20 | 21 | def upgrade() -> None: 22 | """Upgrade schema.""" 23 | ${upgrades if upgrades else "pass"} 24 | 25 | 26 | def downgrade() -> None: 27 | """Downgrade schema.""" 28 | ${downgrades if downgrades else "pass"} 29 | -------------------------------------------------------------------------------- /frontend/src/features/communities/hooks/use-search-communities.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { campuscurrentAPI } from "../api/communitiesApi"; 3 | import { Community } from "@/features/shared/campus/types"; 4 | 5 | export const useSearchCommunities = (params?: { keyword?: string; size?: number }) => { 6 | const keyword = (params?.keyword ?? "").trim(); 7 | const size = params?.size ?? (keyword ? 10 : 20); 8 | 9 | const { data, isLoading, isError } = useQuery>( 10 | campuscurrentAPI.getCommunitiesQueryOptions({ 11 | page: 1, 12 | size, 13 | keyword: keyword || null, 14 | category: null, 15 | }) 16 | ); 17 | 18 | return { 19 | communities: data || null, 20 | isLoading, 21 | isError, 22 | }; 23 | }; 24 | 25 | 26 | -------------------------------------------------------------------------------- /frontend/src/pages/apps/contacts.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ContactsInfoSection } from "@/components/organisms/contacts-info-section"; 4 | import MotionWrapper from "@/components/atoms/motion-wrapper"; 5 | 6 | export default function ContactsPage() { 7 | return ( 8 | 9 |
10 |
11 |

12 | Contacts & Essential Services 13 |

14 |

15 | Save these contacts. In an emergency, call campus security or local services immediately. 16 |

17 |
18 | 19 |
20 |
21 | ); 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/src/components/atoms/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as LabelPrimitive from "@radix-ui/react-label"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/utils/utils"; 6 | 7 | const labelVariants = cva( 8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", 9 | ); 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & 14 | VariantProps 15 | >(({ className, ...props }, ref) => ( 16 | 21 | )); 22 | Label.displayName = LabelPrimitive.Root.displayName; 23 | 24 | export { Label }; 25 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | 23 | /* Paths */ 24 | "baseUrl": ".", 25 | "paths": { 26 | "@/*": ["./src/*"] 27 | } 28 | }, 29 | "include": ["src", "src/types"], 30 | "references": [{ "path": "./tsconfig.node.json" }] 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/features/courses/components/forms/NumericInput.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from "react"; 2 | import { Input, type InputProps } from "@/components/atoms/input"; 3 | import { useIsMacSafari } from "@/hooks/useIsMacSafari"; 4 | 5 | interface NumericInputProps extends InputProps { 6 | allowDecimal?: boolean; 7 | } 8 | 9 | export const NumericInput = forwardRef( 10 | ({ allowDecimal = true, inputMode, ...props }, ref) => { 11 | const isMacSafari = useIsMacSafari(); 12 | const resolvedInputMode = inputMode ?? (allowDecimal ? "decimal" : "numeric"); 13 | 14 | return ( 15 | 20 | ); 21 | }, 22 | ); 23 | 24 | NumericInput.displayName = "NumericInput"; 25 | 26 | -------------------------------------------------------------------------------- /backend/modules/bot/middlewares/db_session.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from typing import Any, Awaitable, Callable 3 | 4 | from aiogram import BaseMiddleware 5 | from aiogram.types import TelegramObject 6 | 7 | from backend.core.database.manager import AsyncDatabaseManager 8 | 9 | 10 | class DatabaseMiddleware(BaseMiddleware): 11 | def __init__(self, db_manager: AsyncDatabaseManager) -> None: 12 | self.db_manager = db_manager 13 | 14 | async def __call__( 15 | self, 16 | handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]], 17 | event: TelegramObject, 18 | data: dict[str, Any], 19 | ) -> Any: 20 | 21 | async with contextlib.asynccontextmanager(self.db_manager.get_async_session)() as session: 22 | data["db_session"] = session 23 | return await handler(event, data) 24 | -------------------------------------------------------------------------------- /backend/modules/courses/courses/base.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException 2 | from fastapi import status as http_status 3 | 4 | from backend.core.database.models.user import UserRole 5 | 6 | 7 | class BasePolicy: 8 | """Base policy with common user attributes.""" 9 | 10 | def __init__(self, user: tuple[dict, dict]): 11 | if not user or not user[0] or not user[1]: 12 | raise HTTPException( 13 | status_code=http_status.HTTP_401_UNAUTHORIZED, 14 | detail="Authentication credentials were not provided", 15 | ) 16 | self.user_creds = user 17 | self.user_role = user[1]["role"] 18 | self.user_sub = user[0]["sub"] 19 | self.is_admin = self.user_role == UserRole.admin.value 20 | 21 | def _is_owner(self, author_sub: str) -> bool: 22 | return self.user_sub == author_sub 23 | -------------------------------------------------------------------------------- /backend/modules/courses/planner/dependencies.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends 2 | from sqlalchemy.ext.asyncio import AsyncSession 3 | 4 | from backend.common.dependencies import get_db_session, get_infra 5 | from backend.common.schemas import Infra 6 | from backend.modules.courses.planner.repository import PlannerRepository 7 | from backend.modules.courses.planner.service import PlannerService 8 | from backend.modules.courses.registrar.service import RegistrarService 9 | 10 | 11 | async def get_planner_service( 12 | db_session: AsyncSession = Depends(get_db_session), 13 | infra: Infra = Depends(get_infra), 14 | ) -> PlannerService: 15 | repository = PlannerRepository(db_session) 16 | registrar_service = RegistrarService(meilisearch_client=infra.meilisearch_client) 17 | return PlannerService(repository=repository, registrar_service=registrar_service) 18 | 19 | -------------------------------------------------------------------------------- /backend/modules/notion/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Iterable, Union 4 | 5 | from backend.common.schemas import Infra 6 | from backend.modules.notion import schemas 7 | from backend.modules.notion.consts import NOTION_QUEUE_NAME 8 | 9 | 10 | 11 | async def send( 12 | *, 13 | infra: Infra, 14 | notion_data: Union[schemas.NotionTicketMessage, Iterable[schemas.NotionTicketMessage]], 15 | ) -> list[schemas.NotionTicketMessage]: 16 | """ 17 | Publish ticket snapshot(s) to the Notion queue. 18 | """ 19 | if isinstance(notion_data, schemas.NotionTicketMessage): 20 | payloads = [notion_data] 21 | else: 22 | payloads = list(notion_data) 23 | 24 | for payload in payloads: 25 | await infra.broker.publish(payload, queue=NOTION_QUEUE_NAME) 26 | 27 | return payloads 28 | 29 | 30 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/buttons/submit-button.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/atoms/button"; 2 | import { RefreshCw } from "lucide-react"; 3 | interface SubmutButtonProps { 4 | isUploading: boolean; 5 | isTelegramLinked: boolean; 6 | uploadProgress: number; 7 | } 8 | export function SubmitButton({ 9 | isUploading, 10 | isTelegramLinked, 11 | uploadProgress, 12 | }: SubmutButtonProps) { 13 | return ( 14 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/data/kp/product.tsx: -------------------------------------------------------------------------------- 1 | import { Blocks, Book, Cable, Shirt, Armchair, WashingMachine, Volleyball, Apple, Bike, Archive} from "lucide-react"; 2 | export const productCategories: Types.DisplayCategory[] = [ 3 | { 4 | title: "All", 5 | icon: Blocks, 6 | }, 7 | { 8 | title: "Books", 9 | icon: Book, 10 | }, 11 | { 12 | title: "Electronics", 13 | icon: Cable, 14 | }, 15 | { 16 | title: "Clothing", 17 | icon: Shirt, 18 | }, 19 | { 20 | title: "Furniture", 21 | icon: Armchair 22 | }, 23 | { 24 | title: "Appliances", 25 | icon: WashingMachine 26 | }, 27 | { 28 | title: "Sports", 29 | icon: Volleyball, 30 | }, 31 | { 32 | title: "Food", 33 | icon: Apple, 34 | }, 35 | { 36 | title: "Transport", 37 | icon: Bike, 38 | }, 39 | { 40 | title: "Others", 41 | icon: Archive, 42 | }, 43 | ]; 44 | -------------------------------------------------------------------------------- /frontend/src/features/communities/hooks/use-edit-community.ts: -------------------------------------------------------------------------------- 1 | import { useMutation, useQueryClient } from "@tanstack/react-query"; 2 | import { campuscurrentAPI } from "../api/communitiesApi"; 3 | import { useParams } from "react-router-dom"; 4 | import { Community } from "@/features/shared/campus/types"; 5 | 6 | export const useEditCommunity = () => { 7 | const { id } = useParams<{ id: string }>(); 8 | const queryClient = useQueryClient(); 9 | 10 | return useMutation({ 11 | mutationFn: (data: Community) => 12 | campuscurrentAPI.editCommunity(data.id.toString(), data), 13 | onSuccess: (data, variables) => { 14 | queryClient.invalidateQueries({ 15 | queryKey: campuscurrentAPI.getCommunityQueryOptions(variables.id.toString()).queryKey, 16 | }); 17 | queryClient.invalidateQueries({ queryKey: ['campusCurrent', 'community', id] }); 18 | }, 19 | }); 20 | }; -------------------------------------------------------------------------------- /frontend/src/components/molecules/BackButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ArrowLeft } from "lucide-react"; 4 | import { cn } from "@/utils/utils"; 5 | import { useBackNavigation } from "@/context/BackNavigationContext"; 6 | 7 | interface BackButtonProps { 8 | className?: string; 9 | label?: string; 10 | } 11 | 12 | export function BackButton({ className, label = "Back" }: BackButtonProps) { 13 | const { triggerBack } = useBackNavigation(); 14 | 15 | return ( 16 | 28 | ); 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/general-section.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | import { Button } from "../atoms/button"; 3 | 4 | export function GeneralSection({ 5 | title, 6 | link, 7 | children, 8 | }: { 9 | title: string; 10 | link: string; 11 | children: React.ReactNode; 12 | }) { 13 | const navigate = useNavigate(); 14 | return ( 15 |
16 |
17 |

{title}

18 | 25 |
26 | 27 |
28 | {children} 29 |
30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/components/atoms/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/utils/utils"; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |