diff --git a/.env.example b/.env.example index 8951384..21b08da 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,7 @@ # ============================================================================= NODE_ENV=development ENVIRONMENT=dev +NEXT_PUBLIC_APP_NAME=roboledger # ============================================================================= # FRONTEND APPLICATION URLS diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 69fa855..eb5a01d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -176,6 +176,7 @@ jobs: - name: Install dependencies and build Next.js if: steps.resolve.outputs.needs_build == 'true' env: + NEXT_PUBLIC_APP_NAME: roboledger NEXT_PUBLIC_ROBOSYSTEMS_API_URL: ${{ inputs.robosystems_api_url }} NEXT_PUBLIC_ROBOLEDGER_APP_URL: ${{ inputs.roboledger_app_url }} NEXT_PUBLIC_ROBOINVESTOR_APP_URL: ${{ inputs.roboinvestor_app_url }} diff --git a/public/images/logo.png b/public/images/logo.png deleted file mode 100644 index 04dd24c..0000000 Binary files a/public/images/logo.png and /dev/null differ diff --git a/public/images/logo_black.png b/public/images/logo_black.png deleted file mode 100644 index f62c478..0000000 Binary files a/public/images/logo_black.png and /dev/null differ diff --git a/src/app/(app)/layout-wrapper.tsx b/src/app/(app)/layout-wrapper.tsx index b59e9f0..c67d008 100644 --- a/src/app/(app)/layout-wrapper.tsx +++ b/src/app/(app)/layout-wrapper.tsx @@ -5,6 +5,7 @@ import { ErrorBoundary } from '@/components/error/ErrorBoundary' import { CoreNavbar, CoreSidebar, + CURRENT_APP, GraphFilters, useGraphContext, useToast, @@ -32,7 +33,7 @@ export function LayoutWrapper({ children }: LayoutWrapperProps) { <> diff --git a/src/app/(landing)/login/content.tsx b/src/app/(landing)/login/content.tsx index ac03676..aca3832 100644 --- a/src/app/(landing)/login/content.tsx +++ b/src/app/(landing)/login/content.tsx @@ -1,6 +1,6 @@ 'use client' -import { SignInForm } from '@/lib/core' +import { CURRENT_APP, SignInForm } from '@/lib/core' export default function LoginContent() { return ( @@ -9,7 +9,7 @@ export default function LoginContent() { process.env.NEXT_PUBLIC_ROBOSYSTEMS_API_URL || 'http://localhost:8000' } enableSSO={true} - currentApp="roboledger" + currentApp={CURRENT_APP} redirectTo="/home" onSuccess={() => {}} onRedirect={(url) => { diff --git a/src/app/(landing)/maintenance.tsx b/src/app/(landing)/maintenance.tsx index b406523..166ed8b 100644 --- a/src/app/(landing)/maintenance.tsx +++ b/src/app/(landing)/maintenance.tsx @@ -1,16 +1,14 @@ 'use client' -import Image from 'next/image' +import { AnimatedLogo } from '@/lib/core/ui-components/Logo' export default function MaintenancePage() { return (
- RoboLedger Logo

RoboLedger diff --git a/src/app/(landing)/register/content.tsx b/src/app/(landing)/register/content.tsx index e5fd130..3465c7a 100644 --- a/src/app/(landing)/register/content.tsx +++ b/src/app/(landing)/register/content.tsx @@ -1,6 +1,6 @@ 'use client' -import { SignUpForm } from '@/lib/core' +import { CURRENT_APP, SignUpForm } from '@/lib/core' export default function RegisterContent() { return ( @@ -8,7 +8,7 @@ export default function RegisterContent() { apiUrl={ process.env.NEXT_PUBLIC_ROBOSYSTEMS_API_URL || 'http://localhost:8000' } - currentApp="roboledger" + currentApp={CURRENT_APP} showConfirmPassword={true} showTermsAcceptance={true} redirectTo="/login" diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e5909ff..a24f017 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -15,7 +15,7 @@ export const metadata: Metadata = { description: 'A comprehensive financial reporting app for private companies to securely share financial reports with shareholders and stakeholders. Built with React, Tailwind CSS, and Flowbite, it provides features such as customizable reports, user permissions, and real-time collaboration.', icons: { - icon: '/images/logo.png', + icon: '/images/logos/roboledger.png', }, } diff --git a/src/components/landing/Footer.tsx b/src/components/landing/Footer.tsx index 646511e..02b3706 100644 --- a/src/components/landing/Footer.tsx +++ b/src/components/landing/Footer.tsx @@ -115,7 +115,10 @@ export default function Footer() {
  • RoboLedger @@ -133,7 +139,10 @@ export default function Footer() {
  • - RoboLedger Logo + RoboLedger diff --git a/src/lib/core/auth-components/AppSwitcher.tsx b/src/lib/core/auth-components/AppSwitcher.tsx index 8d46739..e398694 100644 --- a/src/lib/core/auth-components/AppSwitcher.tsx +++ b/src/lib/core/auth-components/AppSwitcher.tsx @@ -2,11 +2,11 @@ import { Dropdown, DropdownItem } from 'flowbite-react' import { HiViewGrid } from 'react-icons/hi' -import { getAppConfig } from '../auth-core/config' import { useSSO } from '../auth-core/sso' -import type { AppConfig } from '../auth-core/types' +import type { AppConfig, AppName } from '../auth-core/types' import { useToast } from '../hooks/use-toast' import { customTheme } from '../theme' +import { AnimatedLogo } from '../ui-components/Logo' export interface AppSwitcherProps { apiUrl: string @@ -68,13 +68,11 @@ export function AppSwitcher({ onClick={() => handleAppClick(app)} className="flex w-full items-center space-x-3 p-3" > -
    - - {getAppConfig(app.name).initials} - -
    +

    {app.displayName} diff --git a/src/lib/core/auth-components/SignInForm.tsx b/src/lib/core/auth-components/SignInForm.tsx index a653105..a30f6aa 100644 --- a/src/lib/core/auth-components/SignInForm.tsx +++ b/src/lib/core/auth-components/SignInForm.tsx @@ -1,12 +1,11 @@ 'use client' -import Image from 'next/image' import React, { useEffect, useMemo, useState } from 'react' import { RoboSystemsAuthClient } from '../auth-core/client' import { getAppConfig } from '../auth-core/config' import { useSSO } from '../auth-core/sso' import type { AuthUser } from '../auth-core/types' -import { Spinner } from '../ui-components' +import { AnimatedLogo, Spinner } from '../ui-components' export interface SignInFormProps { onSuccess?: (user: AuthUser) => void @@ -139,19 +138,13 @@ export function SignInForm({

    - Logo

    {appName}

    -
    - -
    @@ -162,17 +155,14 @@ export function SignInForm({
    - Logo

    {appName}

    -

    +

    Sign in to your account

    diff --git a/src/lib/core/auth-components/SignUpForm.tsx b/src/lib/core/auth-components/SignUpForm.tsx index 8b894b4..8df07d4 100644 --- a/src/lib/core/auth-components/SignUpForm.tsx +++ b/src/lib/core/auth-components/SignUpForm.tsx @@ -1,11 +1,10 @@ 'use client' -import Image from 'next/image' import React, { useState } from 'react' import { RoboSystemsAuthClient } from '../auth-core/client' import { getAppConfig } from '../auth-core/config' import type { AuthUser } from '../auth-core/types' -import { Spinner } from '../ui-components' +import { AnimatedLogo, Spinner } from '../ui-components' import { TurnstileWidget } from './TurnstileWidget' export interface SignUpFormProps { @@ -146,12 +145,9 @@ export function SignUpForm({
    - Logo

    {appName} diff --git a/src/lib/core/auth-components/__tests__/SignInForm.test.tsx b/src/lib/core/auth-components/__tests__/SignInForm.test.tsx index 7914576..f251ebf 100644 --- a/src/lib/core/auth-components/__tests__/SignInForm.test.tsx +++ b/src/lib/core/auth-components/__tests__/SignInForm.test.tsx @@ -41,6 +41,13 @@ vi.mock('../../ui-components', () => ({ Loading {size} {fullScreen && '(fullscreen)'}

    ), + AnimatedLogo: ({ animate, className }: any) => ( +
    + ), })) const mockUseRouter = vi.mocked(useRouter) @@ -106,10 +113,9 @@ describe('SignInForm', () => { it('should show SSO checking state initially when SSO is enabled', () => { render() - const spinner = screen.getByTestId('spinner') - expect(spinner).toBeInTheDocument() - expect(spinner).toHaveTextContent(/loading/i) - expect(spinner).toHaveTextContent(/xl/i) + const logo = screen.getByTestId('animated-logo') + expect(logo).toBeInTheDocument() + expect(logo).toHaveAttribute('data-animate', 'loop') }) it('should not check SSO when disabled', async () => { diff --git a/src/lib/core/auth-components/__tests__/SignUpForm.test.tsx b/src/lib/core/auth-components/__tests__/SignUpForm.test.tsx index 8b06f2d..140f5cd 100644 --- a/src/lib/core/auth-components/__tests__/SignUpForm.test.tsx +++ b/src/lib/core/auth-components/__tests__/SignUpForm.test.tsx @@ -33,6 +33,13 @@ vi.mock('../../ui-components', () => ({ Loading {size} {fullScreen && '(fullscreen)'}
    ), + AnimatedLogo: ({ animate, className }: any) => ( +
    + ), })) const mockUseRouter = vi.mocked(useRouter) diff --git a/src/lib/core/auth-core/config.ts b/src/lib/core/auth-core/config.ts index c854fd2..c5a8360 100644 --- a/src/lib/core/auth-core/config.ts +++ b/src/lib/core/auth-core/config.ts @@ -1,4 +1,7 @@ -import type { AppConfig } from './types' +import type { AppConfig, AppName } from './types' + +export const CURRENT_APP: AppName = + (process.env.NEXT_PUBLIC_APP_NAME as AppName) || 'robosystems' export const APP_CONFIGS: Record = { roboinvestor: { diff --git a/src/lib/core/auth-core/types.ts b/src/lib/core/auth-core/types.ts index 4e721e0..95ad919 100644 --- a/src/lib/core/auth-core/types.ts +++ b/src/lib/core/auth-core/types.ts @@ -51,6 +51,8 @@ export interface SSOTokenResponse { apps: string[] } +export type AppName = 'robosystems' | 'roboledger' | 'roboinvestor' + export interface AppConfig { name: string displayName: string diff --git a/src/lib/core/index.ts b/src/lib/core/index.ts index 54cb582..a56121d 100644 --- a/src/lib/core/index.ts +++ b/src/lib/core/index.ts @@ -299,4 +299,7 @@ if (process.env.NODE_ENV !== 'test') { export { client } // For backward compatibility, also export the main auth types directly -export type { APIKey, AuthContextType, AuthUser } from './auth-core' +export type { APIKey, AppName, AuthContextType, AuthUser } from './auth-core' + +// App identity +export { CURRENT_APP } from './auth-core/config' diff --git a/src/lib/core/ui-components/Logo.tsx b/src/lib/core/ui-components/Logo.tsx new file mode 100644 index 0000000..f0b64c6 --- /dev/null +++ b/src/lib/core/ui-components/Logo.tsx @@ -0,0 +1,274 @@ +import { CURRENT_APP } from '../auth-core/config' +import type { AppName } from '../auth-core/types' + +export interface AnimatedLogoProps { + className?: string + animate?: 'once' | 'loop' + app?: AppName +} + +/* Graph nodes (shared across all three logos) */ +function GraphNodes() { + return ( + + + + + + + + + + + + + + + + + + + + + ) +} + +/* Shared book structure elements */ +function BookBase() { + return ( + <> + {/* Book left page top */} + + + + {/* Book left side */} + + + + {/* Book right side */} + + + + {/* Book left page */} + + + + {/* Book right page */} + + + + {/* Book top connector */} + + + + + ) +} + +/* RoboInvestor: bar chart animation */ +function InvestorBook({ animate }: { animate: 'once' | 'loop' }) { + const isOnce = animate === 'once' + const animName = isOnce ? 'barIntro' : 'barWave' + const iteration = isOnce ? '1' : 'infinite' + const fillMode = isOnce ? 'forwards' : 'none' + + return ( + <> + + + + {/* Bar 1 (short) */} + + + + + + {/* Bar 2 (medium) */} + + + + + + {/* Bar 3 (tall) */} + + + + + + {/* Spine center */} + + + + + + ) +} + +/* RoboLedger: T-account eyes wink */ +function LedgerBook({ animate }: { animate: 'once' | 'loop' }) { + const isOnce = animate === 'once' + const iteration = isOnce ? '1' : 'infinite' + const fillMode = isOnce ? 'forwards' : 'none' + + return ( + <> + + + + {/* Vertical divider */} + + + + {/* Left eye (T-account line) */} + + + + + + {/* Right eye (T-account line) */} + + + + + + + + ) +} + +/* RoboSystems: infinity S with segment tracing the figure-8 */ +function SystemsBook({ animate }: { animate: 'once' | 'loop' }) { + const isOnce = animate === 'once' + const motionPath = + 'M 16,16 C 22,8 32,10 30,16 C 28,22 22,24 16,16 C 10,8 0,10 2,16 C 4,22 10,24 16,16' + const infinityPath = + 'M0,16C0,17.664 0.405,19.2 1.216,20.608C2.027,22.016 3.136,23.125 4.544,23.936C5.952,24.747 7.488,25.152 9.152,25.152C10.155,25.152 11.179,24.96 12.224,24.576C11.243,23.36 10.485,22.005 9.952,20.512C9.589,20.555 9.323,20.576 9.152,20.576C7.893,20.576 6.816,20.128 5.92,19.232C5.024,18.336 4.576,17.259 4.576,16C4.576,14.741 5.024,13.664 5.92,12.768C6.816,11.872 7.893,11.424 9.152,11.424C10.411,11.424 11.488,11.872 12.384,12.768C13.28,13.664 13.728,14.741 13.728,16C13.728,17.664 14.133,19.2 14.944,20.608C15.755,22.016 16.864,23.125 18.272,23.936C19.68,24.747 21.205,25.152 22.848,25.152C24.491,25.152 26.027,24.747 27.456,23.936C28.885,23.125 29.995,22.016 30.784,20.608C31.573,19.2 31.979,17.664 32,16C32.021,14.336 31.616,12.811 30.784,11.424C29.952,10.037 28.843,8.928 27.456,8.096C26.069,7.264 24.533,6.859 22.848,6.88C21.845,6.88 20.821,7.061 19.776,7.424C20.757,8.64 21.515,10.005 22.048,11.52C22.411,11.456 22.677,11.424 22.848,11.424C24.107,11.424 25.184,11.872 26.08,12.768C26.976,13.664 27.424,14.741 27.424,16C27.424,17.28 26.976,18.368 26.08,19.264C25.184,20.16 24.107,20.597 22.848,20.576C21.589,20.576 20.512,20.139 19.616,19.264C18.72,18.389 18.272,17.301 18.272,16C18.272,14.357 17.867,12.832 17.056,11.424C16.245,10.016 15.136,8.907 13.728,8.096C12.32,7.285 10.795,6.88 9.152,6.88C7.509,6.88 5.973,7.285 4.544,8.096C3.115,8.907 2.005,10.016 1.216,11.424C0.427,12.832 0.021,14.357 -0,16Z' + + return ( + <> + + + {/* Spine center */} + + + + + {/* Infinity S — visible segment traces the figure-8 */} + + {/* Base — starts solid, fades to faint once trace begins */} + + {isOnce ? ( + + ) : ( + + )} + + {/* Bright segment revealed by mask */} + + + + + + + + + + + + ) +} + +export function AnimatedLogo({ + className, + animate = 'loop', + app = CURRENT_APP, +}: AnimatedLogoProps) { + return ( + + {app === 'roboinvestor' && } + {app === 'roboledger' && } + {app === 'robosystems' && } + + + ) +} diff --git a/src/lib/core/ui-components/Spinner.tsx b/src/lib/core/ui-components/Spinner.tsx index bed9f8c..accd7d3 100644 --- a/src/lib/core/ui-components/Spinner.tsx +++ b/src/lib/core/ui-components/Spinner.tsx @@ -1,12 +1,17 @@ +import { CURRENT_APP } from '../auth-core/config' +import type { AppName } from '../auth-core/types' +import { AnimatedLogo } from './Logo' + interface SpinnerProps { size?: 'sm' | 'md' | 'lg' | 'xl' className?: string fullScreen?: boolean + app?: AppName } const sizeClasses = { - sm: 'h-4 w-4', - md: 'h-8 w-8', + sm: 'h-6 w-6', + md: 'h-10 w-10', lg: 'h-16 w-16', xl: 'h-32 w-32', } @@ -15,10 +20,13 @@ export function Spinner({ size = 'md', className = '', fullScreen = false, + app = CURRENT_APP, }: SpinnerProps) { const spinner = ( -
    ) diff --git a/src/lib/core/ui-components/index.ts b/src/lib/core/ui-components/index.ts index 8d2938d..ba7629a 100644 --- a/src/lib/core/ui-components/index.ts +++ b/src/lib/core/ui-components/index.ts @@ -5,6 +5,7 @@ export * from './layout' export * from './settings' // UI Components +export { AnimatedLogo } from './Logo' export { Spinner } from './Spinner' // Chat Components diff --git a/src/lib/core/ui-components/layout/CoreNavbar.tsx b/src/lib/core/ui-components/layout/CoreNavbar.tsx index 54865f5..91cf67b 100644 --- a/src/lib/core/ui-components/layout/CoreNavbar.tsx +++ b/src/lib/core/ui-components/layout/CoreNavbar.tsx @@ -10,7 +10,6 @@ import { NavbarBrand, Tooltip, } from 'flowbite-react' -import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/navigation' import React from 'react' @@ -21,6 +20,7 @@ import { useSidebarContext } from '../../contexts' import { useMediaQuery, useUser } from '../../hooks' import { customTheme } from '../../theme' import type { User } from '../../types' +import { AnimatedLogo } from '../Logo' import { ThemeToggle } from './ThemeToggle' export interface CoreNavbarProps { @@ -136,19 +136,10 @@ export function CoreNavbar({
    - {altText} - {altText} {appName}