Vendor backend and frontend into the monorepo

The submodule collapse was only half applied: .gitmodules was deleted but
backend/ and frontend/ were still recorded as gitlinks, so none of their
files were tracked. Replace the gitlinks with the real trees.

Also untrack .env (it carried placeholder secrets) in favour of a tracked
.env.example, drop the committed __pycache__, and narrow the blanket *.png
ignore that would have swallowed design assets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Melvin Strobl
2026-08-09 15:14:54 +02:00
co-authored by Claude Opus 5
parent 3cb16958ba
commit 1916f7f778
216 changed files with 20353 additions and 54 deletions
+70
View File
@@ -0,0 +1,70 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { useNavigate } from "@tanstack/react-router"
import {
type Body_login_login_access_token as AccessToken,
LoginService,
type UserPublic,
type UserRegister,
UsersService,
} from "@/client"
import { handleError } from "@/utils"
import useCustomToast from "./useCustomToast"
const isLoggedIn = () => {
return localStorage.getItem("access_token") !== null
}
const useAuth = () => {
const navigate = useNavigate()
const queryClient = useQueryClient()
const { showErrorToast } = useCustomToast()
const { data: user } = useQuery<UserPublic | null, Error>({
queryKey: ["currentUser"],
queryFn: UsersService.readUserMe,
enabled: isLoggedIn(),
})
const signUpMutation = useMutation({
mutationFn: (data: UserRegister) =>
UsersService.registerUser({ requestBody: data }),
onSuccess: () => {
navigate({ to: "/login" })
},
onError: handleError.bind(showErrorToast),
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["users"] })
},
})
const login = async (data: AccessToken) => {
const response = await LoginService.loginAccessToken({
formData: data,
})
localStorage.setItem("access_token", response.access_token)
}
const loginMutation = useMutation({
mutationFn: login,
onSuccess: () => {
navigate({ to: "/" })
},
onError: handleError.bind(showErrorToast),
})
const logout = () => {
localStorage.removeItem("access_token")
navigate({ to: "/login" })
}
return {
signUpMutation,
loginMutation,
logout,
user,
}
}
export { isLoggedIn }
export default useAuth
+32
View File
@@ -0,0 +1,32 @@
// source: https://usehooks-ts.com/react-hook/use-copy-to-clipboard
import { useCallback, useState } from "react"
type CopiedValue = string | null
type CopyFn = (text: string) => Promise<boolean>
export function useCopyToClipboard(): [CopiedValue, CopyFn] {
const [copiedText, setCopiedText] = useState<CopiedValue>(null)
const copy: CopyFn = useCallback(async (text) => {
if (!navigator?.clipboard) {
console.warn("Clipboard not supported")
return false
}
try {
await navigator.clipboard.writeText(text)
setCopiedText(text)
setTimeout(() => setCopiedText(null), 2000)
return true
} catch (error) {
console.warn("Copy failed", error)
setCopiedText(null)
return false
}
}, [])
return [copiedText, copy]
}
+19
View File
@@ -0,0 +1,19 @@
import { toast } from "sonner"
const useCustomToast = () => {
const showSuccessToast = (description: string) => {
toast.success("Success!", {
description,
})
}
const showErrorToast = (description: string) => {
toast.error("Something went wrong!", {
description,
})
}
return { showSuccessToast, showErrorToast }
}
export default useCustomToast
+19
View File
@@ -0,0 +1,19 @@
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}