diff --git a/content/docs/guides/reverse-proxy.mdx b/content/docs/guides/reverse-proxy.mdx index 6be3249..1eb304e 100644 --- a/content/docs/guides/reverse-proxy.mdx +++ b/content/docs/guides/reverse-proxy.mdx @@ -11,7 +11,7 @@ TODO To use Traefik as a reverse proxy for OrcaCD, you can add the following labels to your Docker Compose configuration for the OrcaCD Hub service: -```yml +```yml lineNumbers services: hub: image: ghcr.io/orcacd/hub:latest diff --git a/content/docs/helping-out/documentation.mdx b/content/docs/helping-out/documentation.mdx index 4462e3f..e9ad91a 100644 --- a/content/docs/helping-out/documentation.mdx +++ b/content/docs/helping-out/documentation.mdx @@ -17,7 +17,7 @@ All markdown pages are under `/content/docs`. The file `meta.json` controls the 3. Add frontmatter: -```yaml +```yaml lineNumbers --- title: Installation description: Get OrcaCD running quickly with Docker installation diff --git a/content/docs/troubleshooting/account-recovery.mdx b/content/docs/troubleshooting/account-recovery.mdx new file mode 100644 index 0000000..95070f2 --- /dev/null +++ b/content/docs/troubleshooting/account-recovery.mdx @@ -0,0 +1,17 @@ +--- +title: Account Recovery +description: Steps to recover your account if you have lost access +--- + +There are two ways to reset a password for a user: + +1. UI: An admin can reset the password for a user in the admin panel under the "Users" tab by clicking on the three dots next to the user's name and selecting "Edit". +2. Terminal: You can reset a password for a user by running `hub reset-password `. To execute this script with Docker you have to run the following command: + +```bash +docker compose exec hub /app/hub reset-password +``` + +The user with the reset password has to change the password on the next login. + + diff --git a/src/components/account-recovery-terminal.tsx b/src/components/account-recovery-terminal.tsx new file mode 100644 index 0000000..698bc0e --- /dev/null +++ b/src/components/account-recovery-terminal.tsx @@ -0,0 +1,26 @@ +import { AnimatedSpan, Terminal, TypingAnimation } from "./terminal"; + +export function AccountRecoveryTerminal() { + return ( + + + $ docker compose exec hub /app/hub reset-password 019dc111-5220-77bc-9729-2335f88fa658 + + + {` +╭────────────────────────────────────────────────────────────────────╮ +│ │ +│ Password Reset Successful │ +│ │ +│ User ID: 019dc111-5220-77bc-9729-2335f88fa658 │ +│ Email: test@orcacd.dev │ +│ New temporary password: w&BU6G,WM#!MX9M4eq │ +│ │ +│ Important: The user must change this password on the next login. │ +│ │ +╰────────────────────────────────────────────────────────────────────╯ +`} + + + ); +} diff --git a/src/components/terminal.tsx b/src/components/terminal.tsx new file mode 100644 index 0000000..29e8477 --- /dev/null +++ b/src/components/terminal.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { type MotionProps, motion } from "motion/react"; +import { useEffect, useRef, useState } from "react"; + +interface AnimatedSpanProps extends MotionProps { + children: React.ReactNode; + delay?: number; + className?: string; +} + +export const AnimatedSpan = ({ children, delay = 0, className, ...props }: AnimatedSpanProps) => ( + + {children} + +); + +interface TypingAnimationProps extends MotionProps { + children: string; + className?: string; + duration?: number; + delay?: number; + as?: React.ElementType; +} + +export const TypingAnimation = ({ + children, + className, + duration = 60, + delay = 0, + as: Component = "span", + ...props +}: TypingAnimationProps) => { + if (typeof children !== "string") { + throw new Error("TypingAnimation: children must be a string. Received:"); + } + + const MotionComponent = motion.create(Component, { + forwardMotionProps: true, + }); + + const [displayedText, setDisplayedText] = useState(""); + const [started, setStarted] = useState(false); + const elementRef = useRef(null); + + useEffect(() => { + const startTimeout = setTimeout(() => { + setStarted(true); + }, delay); + return () => clearTimeout(startTimeout); + }, [delay]); + + useEffect(() => { + if (!started) { + return; + } + + let i = 0; + const typingEffect = setInterval(() => { + if (i < children.length) { + setDisplayedText(children.substring(0, i + 1)); + i++; + } else { + clearInterval(typingEffect); + } + }, duration); + + return () => { + clearInterval(typingEffect); + }; + }, [children, duration, started]); + + return ( + + {displayedText} + + ); +}; + +interface TerminalProps { + children: React.ReactNode; + className?: string; +} + +export const Terminal = ({ children, className }: TerminalProps) => { + return ( +
+
+
+
+
+
+
+
+
+				{children}
+			
+
+ ); +}; diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..ac680b3 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/src/routes/docs/$.tsx b/src/routes/docs/$.tsx index 9d24fac..0089216 100644 --- a/src/routes/docs/$.tsx +++ b/src/routes/docs/$.tsx @@ -17,6 +17,7 @@ import defaultMdxComponents from "fumadocs-ui/mdx"; import { baseOptions } from "@/lib/layout.shared"; import { getPageMarkdownUrl, source } from "@/lib/source"; import { Suspense } from "react"; +import { AccountRecoveryTerminal } from "@/components/account-recovery-terminal"; export const Route = createFileRoute("/docs/$")({ component: Page, @@ -77,6 +78,7 @@ const clientLoader = browserCollections.docs.createClientLoader({