diff --git a/.env b/.env deleted file mode 100644 index 1d44286e25..0000000000 --- a/.env +++ /dev/null @@ -1,45 +0,0 @@ -# Domain -# This would be set to the production domain with an env var on deployment -# used by Traefik to transmit traffic and aqcuire TLS certificates -DOMAIN=localhost -# To test the local Traefik config -# DOMAIN=localhost.tiangolo.com - -# Used by the backend to generate links in emails to the frontend -FRONTEND_HOST=http://localhost:5173 -# In staging and production, set this env var to the frontend host, e.g. -# FRONTEND_HOST=https://dashboard.example.com - -# Environment: local, staging, production -ENVIRONMENT=local - -PROJECT_NAME="Full Stack FastAPI Project" -STACK_NAME=full-stack-fastapi-project - -# Backend -BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://localhost.tiangolo.com" -SECRET_KEY=changethis -FIRST_SUPERUSER=admin@example.com -FIRST_SUPERUSER_PASSWORD=changethis - -# Emails -SMTP_HOST= -SMTP_USER= -SMTP_PASSWORD= -EMAILS_FROM_EMAIL=info@example.com -SMTP_TLS=True -SMTP_SSL=False -SMTP_PORT=587 - -# Postgres -POSTGRES_SERVER=localhost -POSTGRES_PORT=5432 -POSTGRES_DB=app -POSTGRES_USER=postgres -POSTGRES_PASSWORD=changethis - -SENTRY_DSN= - -# Configure these with your own Docker registry images -DOCKER_IMAGE_BACKEND=backend -DOCKER_IMAGE_FRONTEND=frontend diff --git a/.gitignore b/.gitignore index a6dd346572..9522de3e5c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ node_modules/ /playwright-report/ /blob-report/ /playwright/.cache/ + +.env + diff --git a/backend/.dockerignore b/backend/.dockerignore index c0de4abf73..a9c50f229c 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -6,3 +6,7 @@ app.egg-info .coverage htmlcov .venv + +.env +.env.* +*.env \ No newline at end of file diff --git a/frontend/src/components/Common/Navbar.tsx b/frontend/src/components/Common/Navbar.tsx index 7e952e005e..4fe382063a 100644 --- a/frontend/src/components/Common/Navbar.tsx +++ b/frontend/src/components/Common/Navbar.tsx @@ -19,7 +19,7 @@ function Navbar() { top={0} p={4} > - + Logo diff --git a/frontend/src/components/Common/NotFound.tsx b/frontend/src/components/Common/NotFound.tsx index 9e4f18528e..4266552ab8 100644 --- a/frontend/src/components/Common/NotFound.tsx +++ b/frontend/src/components/Common/NotFound.tsx @@ -31,7 +31,7 @@ const NotFound = () => { The page you are looking for was not found.
- + diff --git a/frontend/src/components/Common/SidebarItems.tsx b/frontend/src/components/Common/SidebarItems.tsx index 13f71495f5..d62c39dd8e 100644 --- a/frontend/src/components/Common/SidebarItems.tsx +++ b/frontend/src/components/Common/SidebarItems.tsx @@ -7,7 +7,7 @@ import type { IconType } from "react-icons/lib" import type { UserPublic } from "@/client" const items = [ - { icon: FiHome, title: "Dashboard", path: "/" }, + { icon: FiHome, title: "Dashboard", path: "/dashboard" }, { icon: FiBriefcase, title: "Items", path: "/items" }, { icon: FiSettings, title: "User Settings", path: "/settings" }, ] diff --git a/frontend/src/components/Landing/OddsCard.tsx b/frontend/src/components/Landing/OddsCard.tsx new file mode 100644 index 0000000000..770cb56e3b --- /dev/null +++ b/frontend/src/components/Landing/OddsCard.tsx @@ -0,0 +1,280 @@ +import { Box, Button, Flex, Image, Text } from "@chakra-ui/react" + +import { useColorModeValue } from "@/components/ui/color-mode" + +type MarketKey = "home" | "draw" | "away" +type OverUnderKey = "over" | "under" + +interface BookInfo { + name: string + logoUrl?: string +} + +interface Market { + value: string + book?: BookInfo +} + +export interface OddsCardProps { + homeTeam: string + awayTeam: string + marketLabel?: string + home: Market + draw: Market + away: Market + onSelect?: (market: MarketKey) => void +} + +export interface OverUnderOddsCardProps { + homeTeam: string + awayTeam: string + marketLabel?: string + line?: string + over: Market + under: Market + onSelect?: (market: OverUnderKey) => void +} + +interface OddsPillProps extends Market { + label: string + onSelect?: () => void +} + +function BookBadge({ logoUrl, name }: BookInfo) { + const badgeBg = useColorModeValue("gray.100", "gray.700") + const badgeColor = useColorModeValue("gray.600", "gray.300") + + return ( + + {logoUrl ? ( + {name} + ) : ( + {name} + )} + + ) +} + +function OddsPill({ value, book, label, onSelect }: OddsPillProps) { + const borderColor = useColorModeValue("gray.200", "gray.600") + const hoverBg = useColorModeValue("gray.100", "gray.700") + const textColor = useColorModeValue("teal.600", "teal.200") + + return ( + + ) +} + +function MarketColumn({ + title, + market, + onSelect, +}: { + title: string + market: Market + onSelect?: () => void +}) { + return ( + + + {title} + + + + ) +} + +export function OddsCard({ + homeTeam, + awayTeam, + marketLabel = "Moneyline", + home, + draw, + away, + onSelect, +}: OddsCardProps) { + const wrapperBg = useColorModeValue("gray.50", "gray.800") + const borderColor = useColorModeValue("gray.200", "gray.600") + + return ( + + + + + {homeTeam} + + + VS + + + {awayTeam} + + + + + {marketLabel} + + + onSelect?.("home")} /> + onSelect?.("draw")} /> + onSelect?.("away")} /> + + + + + ) +} + +export function OverUnderOddsCard({ + homeTeam, + awayTeam, + marketLabel = "Total", + line, + over, + under, + onSelect, +}: OverUnderOddsCardProps) { + const wrapperBg = useColorModeValue("gray.50", "gray.800") + const borderColor = useColorModeValue("gray.200", "gray.600") + + return ( + + + + + {homeTeam} + + + VS + + + {awayTeam} + + + + + {marketLabel} + + {line && ( + + {line} + + )} + + onSelect?.("over")} /> + onSelect?.("under")} /> + + + + + ) +} + +export default OddsCard diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts index 5344493d49..e058a4f1e5 100644 --- a/frontend/src/hooks/useAuth.ts +++ b/frontend/src/hooks/useAuth.ts @@ -51,7 +51,7 @@ const useAuth = () => { const loginMutation = useMutation({ mutationFn: login, onSuccess: () => { - navigate({ to: "/" }) + navigate({ to: "/dashboard" }) }, onError: (err: ApiError) => { handleError(err) diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 8849130b4c..fd1514f9c5 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -13,10 +13,12 @@ import { Route as SignupRouteImport } from './routes/signup' import { Route as ResetPasswordRouteImport } from './routes/reset-password' import { Route as RecoverPasswordRouteImport } from './routes/recover-password' import { Route as LoginRouteImport } from './routes/login' +import { Route as LandingRouteImport } from './routes/landing' import { Route as LayoutRouteImport } from './routes/_layout' -import { Route as LayoutIndexRouteImport } from './routes/_layout/index' +import { Route as IndexRouteImport } from './routes/index' import { Route as LayoutSettingsRouteImport } from './routes/_layout/settings' import { Route as LayoutItemsRouteImport } from './routes/_layout/items' +import { Route as LayoutDashboardRouteImport } from './routes/_layout/dashboard' import { Route as LayoutAdminRouteImport } from './routes/_layout/admin' const SignupRoute = SignupRouteImport.update({ @@ -39,14 +41,19 @@ const LoginRoute = LoginRouteImport.update({ path: '/login', getParentRoute: () => rootRouteImport, } as any) +const LandingRoute = LandingRouteImport.update({ + id: '/landing', + path: '/landing', + getParentRoute: () => rootRouteImport, +} as any) const LayoutRoute = LayoutRouteImport.update({ id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const LayoutIndexRoute = LayoutIndexRouteImport.update({ +const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => LayoutRoute, + getParentRoute: () => rootRouteImport, } as any) const LayoutSettingsRoute = LayoutSettingsRouteImport.update({ id: '/settings', @@ -58,6 +65,11 @@ const LayoutItemsRoute = LayoutItemsRouteImport.update({ path: '/items', getParentRoute: () => LayoutRoute, } as any) +const LayoutDashboardRoute = LayoutDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => LayoutRoute, +} as any) const LayoutAdminRoute = LayoutAdminRouteImport.update({ id: '/admin', path: '/admin', @@ -65,73 +77,87 @@ const LayoutAdminRoute = LayoutAdminRouteImport.update({ } as any) export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/landing': typeof LandingRoute '/login': typeof LoginRoute '/recover-password': typeof RecoverPasswordRoute '/reset-password': typeof ResetPasswordRoute '/signup': typeof SignupRoute '/admin': typeof LayoutAdminRoute + '/dashboard': typeof LayoutDashboardRoute '/items': typeof LayoutItemsRoute '/settings': typeof LayoutSettingsRoute - '/': typeof LayoutIndexRoute } export interface FileRoutesByTo { + '/': typeof IndexRoute + '/landing': typeof LandingRoute '/login': typeof LoginRoute '/recover-password': typeof RecoverPasswordRoute '/reset-password': typeof ResetPasswordRoute '/signup': typeof SignupRoute '/admin': typeof LayoutAdminRoute + '/dashboard': typeof LayoutDashboardRoute '/items': typeof LayoutItemsRoute '/settings': typeof LayoutSettingsRoute - '/': typeof LayoutIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport + '/': typeof IndexRoute '/_layout': typeof LayoutRouteWithChildren + '/landing': typeof LandingRoute '/login': typeof LoginRoute '/recover-password': typeof RecoverPasswordRoute '/reset-password': typeof ResetPasswordRoute '/signup': typeof SignupRoute '/_layout/admin': typeof LayoutAdminRoute + '/_layout/dashboard': typeof LayoutDashboardRoute '/_layout/items': typeof LayoutItemsRoute '/_layout/settings': typeof LayoutSettingsRoute - '/_layout/': typeof LayoutIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: + | '/' + | '/landing' | '/login' | '/recover-password' | '/reset-password' | '/signup' | '/admin' + | '/dashboard' | '/items' | '/settings' - | '/' fileRoutesByTo: FileRoutesByTo to: + | '/' + | '/landing' | '/login' | '/recover-password' | '/reset-password' | '/signup' | '/admin' + | '/dashboard' | '/items' | '/settings' - | '/' id: | '__root__' + | '/' | '/_layout' + | '/landing' | '/login' | '/recover-password' | '/reset-password' | '/signup' | '/_layout/admin' + | '/_layout/dashboard' | '/_layout/items' | '/_layout/settings' - | '/_layout/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { + IndexRoute: typeof IndexRoute LayoutRoute: typeof LayoutRouteWithChildren + LandingRoute: typeof LandingRoute LoginRoute: typeof LoginRoute RecoverPasswordRoute: typeof RecoverPasswordRoute ResetPasswordRoute: typeof ResetPasswordRoute @@ -168,6 +194,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } + '/landing': { + id: '/landing' + path: '/landing' + fullPath: '/landing' + preLoaderRoute: typeof LandingRouteImport + parentRoute: typeof rootRouteImport + } '/_layout': { id: '/_layout' path: '' @@ -175,12 +208,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/_layout/': { - id: '/_layout/' + '/': { + id: '/' path: '/' fullPath: '/' - preLoaderRoute: typeof LayoutIndexRouteImport - parentRoute: typeof LayoutRoute + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport } '/_layout/settings': { id: '/_layout/settings' @@ -196,6 +229,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutItemsRouteImport parentRoute: typeof LayoutRoute } + '/_layout/dashboard': { + id: '/_layout/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof LayoutDashboardRouteImport + parentRoute: typeof LayoutRoute + } '/_layout/admin': { id: '/_layout/admin' path: '/admin' @@ -208,23 +248,25 @@ declare module '@tanstack/react-router' { interface LayoutRouteChildren { LayoutAdminRoute: typeof LayoutAdminRoute + LayoutDashboardRoute: typeof LayoutDashboardRoute LayoutItemsRoute: typeof LayoutItemsRoute LayoutSettingsRoute: typeof LayoutSettingsRoute - LayoutIndexRoute: typeof LayoutIndexRoute } const LayoutRouteChildren: LayoutRouteChildren = { LayoutAdminRoute: LayoutAdminRoute, + LayoutDashboardRoute: LayoutDashboardRoute, LayoutItemsRoute: LayoutItemsRoute, LayoutSettingsRoute: LayoutSettingsRoute, - LayoutIndexRoute: LayoutIndexRoute, } const LayoutRouteWithChildren = LayoutRoute._addFileChildren(LayoutRouteChildren) const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, LayoutRoute: LayoutRouteWithChildren, + LandingRoute: LandingRoute, LoginRoute: LoginRoute, RecoverPasswordRoute: RecoverPasswordRoute, ResetPasswordRoute: ResetPasswordRoute, diff --git a/frontend/src/routes/_layout/index.tsx b/frontend/src/routes/_layout/dashboard.tsx similarity index 89% rename from frontend/src/routes/_layout/index.tsx rename to frontend/src/routes/_layout/dashboard.tsx index 66e32e0b30..44e90d389c 100644 --- a/frontend/src/routes/_layout/index.tsx +++ b/frontend/src/routes/_layout/dashboard.tsx @@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router" import useAuth from "@/hooks/useAuth" -export const Route = createFileRoute("/_layout/")({ +export const Route = createFileRoute("/_layout/dashboard")({ component: Dashboard, }) diff --git a/frontend/src/routes/index.tsx b/frontend/src/routes/index.tsx new file mode 100644 index 0000000000..f08040d11a --- /dev/null +++ b/frontend/src/routes/index.tsx @@ -0,0 +1,24 @@ +import { useEffect } from "react" + +import { createFileRoute, useNavigate } from "@tanstack/react-router" + +import { isLoggedIn } from "@/hooks/useAuth" + +export const Route = createFileRoute("/")({ + component: RootRedirect, +}) + +function RootRedirect() { + const navigate = useNavigate() + + useEffect(() => { + navigate({ + to: isLoggedIn() ? "/dashboard" : "/landing", + replace: true, + }) + }, [navigate]) + + return null +} + +export default RootRedirect diff --git a/frontend/src/routes/landing.tsx b/frontend/src/routes/landing.tsx new file mode 100644 index 0000000000..c500b5bbb7 --- /dev/null +++ b/frontend/src/routes/landing.tsx @@ -0,0 +1,171 @@ +import { Box, Button, Container, Flex, Heading, Text } from "@chakra-ui/react" +import { Link as RouterLink, createFileRoute } from "@tanstack/react-router" + +import { isLoggedIn } from "@/hooks/useAuth" +import { OddsCard, OverUnderOddsCard } from "@/components/Landing/OddsCard" +import { ColorModeButton } from "@/components/ui/color-mode" +import useCustomToast from "@/hooks/useCustomToast" + +export const Route = createFileRoute("/landing")({ + component: LandingPage, +}) + +function LandingPage() { + const loggedIn = isLoggedIn() + const { showSuccessToast } = useCustomToast() + + const featuredMarket = { + homeTeam: "Tampines Rovers", + awayTeam: "Pohang Steelers", + marketLabel: "Moneyline", + home: { + value: "+640", + book: { + name: "BC.GAME", + logoUrl: "https://dummyimage.com/60x18/eeeeee/333333&text=BC.GAME", + }, + }, + draw: { + value: "+350", + book: { + name: "MOSTBET", + logoUrl: "https://dummyimage.com/64x18/eeeeee/333333&text=MOSTBET", + }, + }, + away: { + value: "-129", + book: { + name: "betway", + logoUrl: "https://dummyimage.com/64x18/eeeeee/333333&text=betway", + }, + }, + } + + const totalsMarket = { + homeTeam: "Real Betis", + awayTeam: "Sevilla FC", + marketLabel: "Total Goals", + line: "2.5", + over: { + value: "-105", + book: { + name: "FanDuel", + logoUrl: "https://dummyimage.com/64x18/eeeeee/333333&text=FanDuel", + }, + }, + under: { + value: "-110", + book: { + name: "DraftKings", + logoUrl: "https://dummyimage.com/64x18/eeeeee/333333&text=DraftKings", + }, + }, + } + + return ( + + {/* + + + + + Seamless Arbitrage Insights + + + Track market opportunities, monitor performance, and make decisions + with confidence. All from a single, intuitive dashboard. + + + + + + {!loggedIn && ( + + + + )} + + */} + + {/* + + + + */} + + + + + + showSuccessToast( + `Selected ${market.toUpperCase()} market from the featured matchup.`, + ) + } + /> + + + + + + showSuccessToast( + `Selected ${market.toUpperCase()} option on the featured totals market.`, + ) + } + /> + + + ) +} + +interface FeatureProps { + title: string + description: string +} + +function Feature({ title, description }: FeatureProps) { + return ( + + + {title} + + {description} + + ) +} + +export default LandingPage diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index 2e0539d9d9..49f2d680e1 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -21,7 +21,7 @@ export const Route = createFileRoute("/login")({ beforeLoad: async () => { if (isLoggedIn()) { throw redirect({ - to: "/", + to: "/dashboard", }) } }, diff --git a/frontend/src/routes/recover-password.tsx b/frontend/src/routes/recover-password.tsx index 084fbdd73a..b7144fc9b4 100644 --- a/frontend/src/routes/recover-password.tsx +++ b/frontend/src/routes/recover-password.tsx @@ -21,7 +21,7 @@ export const Route = createFileRoute("/recover-password")({ beforeLoad: async () => { if (isLoggedIn()) { throw redirect({ - to: "/", + to: "/dashboard", }) } }, diff --git a/frontend/src/routes/reset-password.tsx b/frontend/src/routes/reset-password.tsx index f55f49e287..04598696aa 100644 --- a/frontend/src/routes/reset-password.tsx +++ b/frontend/src/routes/reset-password.tsx @@ -20,7 +20,7 @@ export const Route = createFileRoute("/reset-password")({ beforeLoad: async () => { if (isLoggedIn()) { throw redirect({ - to: "/", + to: "/dashboard", }) } }, diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx index 6e7890c485..5bc9708cb7 100644 --- a/frontend/src/routes/signup.tsx +++ b/frontend/src/routes/signup.tsx @@ -21,7 +21,7 @@ export const Route = createFileRoute("/signup")({ beforeLoad: async () => { if (isLoggedIn()) { throw redirect({ - to: "/", + to: "/dashboard", }) } },