This commit is contained in:
James Ketr 2025-05-19 01:02:52 -07:00
parent b1faac7020
commit a73d083c2b
31 changed files with 5981 additions and 1915 deletions

View File

View File

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@
"@uiw/react-json-view": "^2.0.0-alpha.31",
"@uiw/react-markdown-editor": "^6.1.4",
"jsonrepair": "^3.12.0",
"lucide-react": "^0.511.0",
"markdown-it": "^14.1.0",
"mermaid": "^11.6.0",
"mui-markdown": "^2.0.1",

View File

@ -0,0 +1,15 @@
I am happy to announce the first public BETA release of Backstory!
First, what works:
1. There are two personas populated:
1. One is me [jketreno](/u/jketreno)
2. The other is a ficticious AI generated persona named [Eliz](/u/eliza).
2. **Candidate Skill Chat** You can go to the Chat tab to ask questions about the active candaite.
3. **Resume Builder** You can build a resume for a person given a Job Description
What doesn't work:
1. User login, registration, etc.
2. Lots of the links on the site.
3. Anything that isn't "Chat", "Resume Builder", or "About".

View File

@ -2,9 +2,10 @@ import React, { useRef, useCallback } from 'react';
import { BrowserRouter as Router, Routes, Route, useLocation } from "react-router-dom";
import { SessionWrapper } from "./App/SessionWrapper";
import { Main } from "./App/Main";
import { BackstoryApp } from './NewApp/BackstoryApp';
import { Snack, SeverityType } from './Components/Snack';
export function PathRouter({ setSnack }: { setSnack: any }) {
const PathRouter = ({ setSnack }: { setSnack: any }) => {
const location = useLocation();
const segments = location.pathname.split("/").filter(Boolean);
const sessionId = segments[segments.length - 1];
@ -16,7 +17,7 @@ export function PathRouter({ setSnack }: { setSnack: any }) {
);
}
function App() {
function App2() {
const snackRef = useRef<any>(null);
const setSnack = useCallback((message: string, severity?: SeverityType) => {
@ -37,4 +38,13 @@ function App() {
);
}
const App = () => {
return (
<Router>
<BackstoryApp />
</Router>
);
};
export default App;

View File

@ -500,17 +500,18 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>((props: C
};
return (
<Scrollable
className={`${className || ""} Conversation`}
autoscroll
textFieldRef={viewableElementRef}
fallbackThreshold={0.5}
sx={{
p: 1,
mt: 0,
...sx
}}
>
// <Scrollable
// className={`${className || ""} Conversation`}
// autoscroll
// textFieldRef={viewableElementRef}
// fallbackThreshold={0.5}
// sx={{
// p: 1,
// mt: 0,
// ...sx
// }}
// >
<Box sx={{ p: 1, mt: 0, overflow: "hidden", ...sx }}>
{
filteredConversation.map((message, index) =>
<Message key={index} expanded={message.expanded === undefined ? true : message.expanded} {...{ sendQuery, message, connectionBase, sessionId, setSnack, submitQuery }} />
@ -610,7 +611,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>((props: C
}
</Box>
<Box sx={{ display: "flex", flexGrow: 1 }}></Box>
</Scrollable>
</Box >
);
});

View File

@ -1,18 +1,22 @@
import React, { useState, useEffect } from 'react';
import { Box } from '@mui/material';
import { Message } from './Message';
import { ChatBubble } from '../Components/ChatBubble';
import { BackstoryElementProps } from './BackstoryTab';
interface DocumentProps extends BackstoryElementProps {
title: string;
expanded?: boolean;
filepath: string;
filepath?: string;
content?: string;
disableCopy?: boolean;
children?: React.ReactNode;
onExpand?: (open: boolean) => void;
}
const Document = (props: DocumentProps) => {
const { sessionId, setSnack, submitQuery, filepath, content, title, expanded, disableCopy, onExpand } = props;
const { sessionId, setSnack, submitQuery, filepath, content, title, expanded, disableCopy, onExpand, children } = props;
const backstoryProps = {
submitQuery,
setSnack,
@ -49,7 +53,27 @@ const Document = (props: DocumentProps) => {
}, [document, setDocument, filepath])
return (
<Message
<Box>
{children !== undefined && <ChatBubble
{...{
sx: {
mt: 1,
p: 1,
flexGrow: 0,
display: "flex",
flexDirection: "column",
pt: "8px",
pb: "8px",
marginTop: "8px !important", // Remove whitespace from expanded Accordion
marginBottom: "0px !important", // Remove whitespace from expanded Accordion
},
role: "content",
title,
expanded,
disableCopy,
onExpand
}}>{children}</ChatBubble>}
{children === undefined && <Message
{...{
sx: {
display: 'flex',
@ -57,6 +81,7 @@ const Document = (props: DocumentProps) => {
p: 1,
m: 0,
flexGrow: 0,
marginTop: "8px !important", // Remove whitespace from expanded Accordion
},
message: { role: 'content', title: title, content: document || content || "" },
expanded,
@ -64,6 +89,8 @@ const Document = (props: DocumentProps) => {
onExpand,
}}
{...backstoryProps} />
}
</Box>
);
};

View File

@ -0,0 +1,247 @@
import React, { ReactElement, useEffect, useState, useRef, useCallback} from 'react';
import { Box, Typography, Container, Paper } from '@mui/material';
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import DashboardIcon from '@mui/icons-material/Dashboard';
import PersonIcon from '@mui/icons-material/Person';
import ChatIcon from '@mui/icons-material/Chat';
import HistoryIcon from '@mui/icons-material/History';
import DescriptionIcon from '@mui/icons-material/Description';
import QuestionAnswerIcon from '@mui/icons-material/QuestionAnswer';
import BarChartIcon from '@mui/icons-material/BarChart';
import SettingsIcon from '@mui/icons-material/Settings';
import SearchIcon from '@mui/icons-material/Search';
import BookmarkIcon from '@mui/icons-material/Bookmark';
import WorkIcon from '@mui/icons-material/Work';
import InfoIcon from '@mui/icons-material/Info';
import BusinessIcon from '@mui/icons-material/Business';
import { SxProps, Theme } from '@mui/material';
import { ThemeProvider } from '@mui/material/styles';
import {Header} from './Components/Header';
import { Scrollable } from '../Components/Scrollable';
import { Footer } from './Components/Footer';
import { HomePage } from './Pages/HomePage';
// import { BackstoryThemeVisualizer } from './BackstoryThemeVisualizer';
import { HomePage as ChatPage } from '../Pages/HomePage';
import { ResumeBuilderPage } from '../Pages/ResumeBuilderPage';
import { Snack, SeverityType } from '../Components/Snack';
import { Query } from '../Components/ChatQuery';
import { ConversationHandle } from '../Components/Conversation';
import { AboutPage } from './Pages/AboutPage';
import { backstoryTheme } from '../BackstoryTheme';
import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
type NavigationLinkType = {
name: string;
path: string;
icon?: ReactElement<any>;
label?: ReactElement<any>;
};
const DefaultNavItems : NavigationLinkType[] = [
{ name: 'Chat', path: '/chat', icon: <ChatIcon /> },
{ name: 'Resume Builder', path: '/resume-builder', icon: <WorkIcon /> },
{ name: 'About', path: '/about', icon: <InfoIcon /> },
// { name: 'How It Works', path: '/how-it-works', icon: <InfoIcon/> },
// { name: 'For Candidates', path: '/for-candidates', icon: <PersonIcon/> },
// { name: 'For Employers', path: '/for-employers', icon: <BusinessIcon/> },
// { name: 'Pricing', path: '/pricing', icon: <AttachMoneyIcon/> },
];
const CandidateNavItems : NavigationLinkType[]= [
{ name: 'Dashboard', icon: <DashboardIcon />, path: '/dashboard' },
{ name: 'Profile', icon: <PersonIcon />, path: '/profile' },
{ name: 'Backstory', icon: <HistoryIcon />, path: '/backstory' },
{ name: 'Resumes', icon: <DescriptionIcon />, path: '/resumes' },
{ name: 'Q&A Setup', icon: <QuestionAnswerIcon />, path: '/qa-setup' },
{ name: 'Analytics', icon: <BarChartIcon />, path: '/analytics' },
{ name: 'Settings', icon: <SettingsIcon />, path: '/settings' },
];
const EmployerNavItems: NavigationLinkType[] = [
{ name: 'Dashboard', icon: <DashboardIcon />, path: '/dashboard' },
{ name: 'Search', icon: <SearchIcon />, path: '/search' },
{ name: 'Saved', icon: <BookmarkIcon />, path: '/saved' },
{ name: 'Jobs', icon: <WorkIcon />, path: '/jobs' },
{ name: 'Company', icon: <BusinessIcon />, path: '/company' },
{ name: 'Analytics', icon: <BarChartIcon />, path: '/analytics' },
{ name: 'Settings', icon: <SettingsIcon />, path: '/settings' },
];
// Navigation links based on user type
const getNavigationLinks = (userType: string, isLoggedIn: boolean) : NavigationLinkType[] => {
if (!isLoggedIn) {
return DefaultNavItems;
}
if (userType === 'candidate') {
return CandidateNavItems;
}
// Employer navigation
return EmployerNavItems;
};
// Placeholder for page components
const DashboardPage = () => <Box p={3}><Typography variant="h4">Dashboard</Typography></Box>;
const ProfilePage = () => <Box p={3}><Typography variant="h4">Profile</Typography></Box>;
const BackstoryPage = () => <Box p={3}><Typography variant="h4">Backstory</Typography></Box>;
const ResumesPage = () => <Box p={3}><Typography variant="h4">Resumes</Typography></Box>;
const QASetupPage = () => <Box p={3}><Typography variant="h4">Q&A Setup</Typography></Box>;
const AnalyticsPage = () => <Box p={3}><Typography variant="h4">Analytics</Typography></Box>;
const SettingsPage = () => <Box p={3}><Typography variant="h4">Settings</Typography></Box>;
const SearchPage = () => <Box p={3}><Typography variant="h4">Search</Typography></Box>;
const SavedPage = () => <Box p={3}><Typography variant="h4">Saved</Typography></Box>;
const JobsPage = () => <Box p={3}><Typography variant="h4">Jobs</Typography></Box>;
const CompanyPage = () => <Box p={3}><Typography variant="h4">Company</Typography></Box>;
// This is a placeholder for your actual user context
type UserContext = {
user: {
type: string;
name: string;
avatar?: any;
};
isAuthenticated: boolean;
logout: () => void;
};
const useUserContext = () : UserContext => {
return {
user: {
type: 'candidate', // or 'employer'
name: 'John Doe',
avatar: null,
},
isAuthenticated: false,
logout: () => console.log('Logging out'),
};
};
interface BackstoryPageContainerProps {
children?: React.ReactNode;
sx?: SxProps<Theme>;
userContext: UserContext;
};
const BackstoryPageContainer = (props : BackstoryPageContainerProps) => {
const { children, sx } = props;
return (
<Container maxWidth="xl" sx={{ mt: 2, mb: 2, ...sx }}>
<Paper
elevation={2}
sx={{
p: 3,
backgroundColor: 'background.paper',
borderRadius: 2,
minHeight: '80vh',
}}
>
<Scrollable>
{children}
</Scrollable>
</Paper>
</Container>
);
}
const BackstoryApp = () => {
const navigate = useNavigate();
const location = useLocation();
const userContext : UserContext = useUserContext();
const { user, isAuthenticated } = userContext;
const [navigationLinks, setNavigationLinks] = useState<NavigationLinkType[]>([]);
const snackRef = useRef<any>(null);
const chatRef = useRef<ConversationHandle>(null);
const setSnack = useCallback((message: string, severity?: SeverityType) => {
snackRef.current?.setSnack(message, severity);
}, [snackRef]);
const submitQuery = (query: Query) => {
console.log(`handleSubmitChatQuery:`, query, chatRef.current ? ' sending' : 'no handler');
chatRef.current?.submitQuery(query);
navigate('/chat');
};
const [page, setPage] = useState<string>("");
useEffect(() => {
const currentRoute = location.pathname.split("/")[1] ? `/${location.pathname.split("/")[1]}` : "/";
setPage(currentRoute);
}, [location.pathname]);
useEffect(() => {
setNavigationLinks(getNavigationLinks(user.type, isAuthenticated));
}, [user.type, isAuthenticated]);
// Render appropriate routes based on user type
return (
<ThemeProvider theme={backstoryTheme}>
<Header currentPath={page} navigate={navigate} navigationLinks={navigationLinks} showLogin={false}/>
<Box sx={{ display: "flex", minHeight: "72px", height: "72px" }}/>
<Scrollable sx={{
display: 'flex',
flexDirection: 'column',
backgroundColor: 'background.default',
maxHeight: "calc(100vh - 72px)",
}}>
<BackstoryPageContainer userContext={userContext}>
<Routes>
<Route path="/chat" element={<ChatPage ref={chatRef} setSnack={setSnack} sessionId={"684a0c7e-e638-4db7-b00d-0558bfefb710"} submitQuery={submitQuery}/>} />
<Route path="/about" element={<AboutPage setSnack={setSnack} sessionId={"684a0c7e-e638-4db7-b00d-0558bfefb710"} submitQuery={submitQuery}/>} />
<Route path="/about/:subPage" element={<AboutPage setSnack={setSnack} sessionId={"684a0c7e-e638-4db7-b00d-0558bfefb710"} submitQuery={submitQuery}/>} />
<Route path="/resume-builder" element={<ResumeBuilderPage setSnack={setSnack} sessionId={"684a0c7e-e638-4db7-b00d-0558bfefb710"} submitQuery={submitQuery}/>} />
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/" element={<HomePage/>}/>
{/* Candidate-specific routes */}
{user.type === 'candidate' && (
<>
<Route path="/profile" element={<ProfilePage />} />
<Route path="/backstory" element={<BackstoryPage />} />
<Route path="/resumes" element={<ResumesPage />} />
<Route path="/qa-setup" element={<QASetupPage />} />
</>
)}
{/* Employer-specific routes */}
{user.type === 'employer' && (
<>
<Route path="/search" element={<SearchPage />} />
<Route path="/saved" element={<SavedPage />} />
<Route path="/jobs" element={<JobsPage />} />
<Route path="/company" element={<CompanyPage />} />
</>
)}
{/* Common routes */}
<Route path="/analytics" element={<AnalyticsPage />} />
<Route path="/settings" element={<SettingsPage />} />
{/* Redirect to dashboard by default */}
<Route path="*" element={<HomePage />} />
</Routes>
</BackstoryPageContainer>
{ location.pathname === "/" && <Footer /> }
</Scrollable>
<Snack ref={snackRef}/>
</ThemeProvider>
);
};
const App = () => {
return (
<BackstoryApp />
);
};
export type {
UserContext,
NavigationLinkType
};
export {
BackstoryApp
};

View File

@ -0,0 +1,123 @@
import { createTheme } from '@mui/material/styles';
const backstoryTheme = createTheme({
palette: {
primary: {
main: '#1A2536', // Midnight Blue
contrastText: '#D3CDBF', // Warm Gray
},
secondary: {
main: '#4A7A7D', // Dusty Teal
contrastText: '#FFFFFF', // White
},
text: {
primary: '#2E2E2E', // Charcoal Black
secondary: '#1A2536', // Midnight Blue
},
background: {
default: '#D3CDBF', // Warm Gray
paper: '#FFFFFF', // White
},
action: {
active: '#D4A017', // Golden Ochre
hover: 'rgba(212, 160, 23, 0.1)', // Golden Ochre with opacity
},
custom: {
highlight: '#D4A017', // Golden Ochre
contrast: '#2E2E2E', // Charcoal Black
},
},
typography: {
fontFamily: "'Roboto', sans-serif",
h1: {
fontSize: '2rem',
fontWeight: 500,
color: '#2E2E2E', // Charcoal Black
},
h2: {
fontSize: '1.75rem',
fontWeight: 500,
color: '#2E2E2E', // Charcoal Black
marginBottom: '1rem',
},
h3: {
fontSize: '1.5rem',
fontWeight: 500,
color: '#2E2E2E', // Charcoal Black
marginBottom: '0.75rem',
},
h4: {
fontSize: '1.25rem',
fontWeight: 500,
color: '#2E2E2E', // Charcoal Black
marginBottom: '0.5rem',
},
body1: {
fontSize: '1rem',
color: '#2E2E2E', // Charcoal Black
marginBottom: '0.5rem',
},
body2: {
fontSize: '0.875rem',
color: '#2E2E2E', // Charcoal Black
},
},
components: {
MuiLink: {
styleOverrides: {
root: {
color: '#4A7A7D', // Dusty Teal (your secondary color)
textDecoration: 'none',
'&:hover': {
color: '#D4A017', // Golden Ochre on hover
textDecoration: 'underline',
},
},
},
},
MuiButton: {
styleOverrides: {
root: {
textTransform: 'none',
'&:hover': {
backgroundColor: 'rgba(212, 160, 23, 0.2)', // Golden Ochre hover
},
},
},
},
MuiAppBar: {
styleOverrides: {
root: {
backgroundColor: '#1A2536', // Midnight Blue
},
},
},
MuiPaper: {
styleOverrides: {
root: {
padding: '2rem',
borderRadius: '8px',
},
},
},
MuiList: {
styleOverrides: {
root: {
padding: '0.5rem',
},
},
},
MuiListItem: {
styleOverrides: {
root: {
borderRadius: '4px',
'&:hover': {
backgroundColor: 'rgba(212, 160, 23, 0.1)', // Golden Ochre with opacity
},
},
},
},
},
});
export { backstoryTheme };

View File

@ -0,0 +1,151 @@
.beta-clipper.mobile {
left: 0px;
}
.beta-clipper {
position: absolute;
top: 0;
left: -5px;
width: 100%;
height: 72px;
overflow: hidden;
pointer-events: none;
z-index: 1101;
cursor: pointer;
font-family: 'Roboto';
line-height: 40px;
}
.beta-label.mobile::before {
margin-left: -15px;
}
.beta-label::before {
padding-left: 10px;
content: "";
}
.beta-label.mobile {
transform: rotate(45deg);
right: 0px;
}
.beta-label {
width: 200px;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
transform: rotate(-45deg);
transform-origin: bottom center;
font-size: 28px;
text-align: center;
font-weight: bold;
color: #d8d8d8;
background: rgba(255, 215, 0, 0.5);
border: 1px solid gold;
padding: 8px;
z-index: 11;
pointer-events: auto;
box-shadow: 0 0 4px rgba(255, 215, 0, 0.6), 0 0 10px rgba(255, 215, 0, 0.4), 0 0 16px rgba(255, 215, 0, 0.2);
}
.beta-label:hover {
color: white;
animation: glow-pulse 1.2s infinite ease-in-out;
}
.beta-label.animate {
animation: text-pulse 1.5s ease-in-out;
animation-delay: 1s;
}
.particles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
}
.particles::before {
content: "";
position: absolute;
width: 200%;
height: 200%;
left: -50%;
top: -50%;
pointer-events: none;
background: radial-gradient(circle, rgba(255, 255, 255, 0.95) 0%, transparent 70%) -15% 110%,
radial-gradient(circle, rgba(173, 216, 255, 0.95) 0%, transparent 70%) 83% 120%,
radial-gradient(circle, rgba(255, 255, 200, 0.9) 0%, transparent 70%) -8% 95%,
radial-gradient(circle, rgba(200, 240, 255, 0.9) 0%, transparent 70%) 115% 105%,
radial-gradient(circle, rgba(255, 255, 255, 0.9) 0%, transparent 70%) 42% 130%,
radial-gradient(circle, rgba(255, 255, 255, 0.95) 0%, transparent 50%) -20% 80%,
radial-gradient(circle, rgba(200, 230, 255, 0.9) 0%, transparent 40%) 125% 90%,
radial-gradient(circle, rgba(255, 240, 200, 0.85) 0%, transparent 60%) 35% 135%,
radial-gradient(circle, rgba(220, 240, 255, 0.9) 0%, transparent 55%) -25% 110%,
radial-gradient(circle, rgba(255, 255, 230, 0.9) 0%, transparent 65%) 110% 70%,
radial-gradient(circle, rgba(170, 220, 255, 0.85) 0%, transparent 65%) 20% 140%,
radial-gradient(circle, rgba(255, 255, 255, 0.9) 0%, transparent 60%) -10% 75%,
radial-gradient(circle, rgba(255, 250, 220, 0.85) 0%, transparent 75%) 130% 115%,
radial-gradient(circle, rgba(210, 235, 255, 0.95) 0%, transparent 80%) 5% 125%,
radial-gradient(circle, rgba(255, 255, 255, 0.8) 0%, transparent 75%) -30% 100%;
background-repeat: no-repeat;
background-size: 10px 10px, 10px 10px, 10px 10px, 10px 10px, 10px 10px, 5px 5px, 6px 6px, 8px 8px, 7px 7px, 11px 11px,
10px 10px, 9px 9px, 14px 14px, 15px 15px, 12px 12px;
filter: blur(0.5px);
opacity: 0;
animation-name: none;
}
.beta-label.animate .particles::before {
animation-name: beam-sparkles;
animation-duration: 2s;
animation-timing-function: ease-out;
animation-fill-mode: forwards;
}
@keyframes glow-pulse {
0% {
box-shadow: 0 0 6px rgba(255, 215, 0, 0.5);
}
50% {
box-shadow: 0 0 12px rgba(255, 215, 0, 0.8);
}
100% {
box-shadow: 0 0 6px rgba(255, 215, 0, 0.5);
}
}
@keyframes text-pulse {
0% {
color: #d8d8d8;
}
50% {
color: gold;
}
100% {
color: #d8d8d8;
}
}
@keyframes beam-sparkles {
0% {
opacity: 1;
background-position: -15% 110%, 83% 120%, -8% 95%, 115% 105%, 42% 130%, -20% 80%, 125% 90%, 35% 135%, -25% 110%,
110% 70%, 20% 140%, -10% 75%, 130% 115%, 5% 125%, -30% 100%;
}
25% {
background-position: 0% 90%, 75% 95%, 5% 85%, 100% 85%, 45% 100%, -5% 70%, 105% 75%, 40% 110%, -5% 90%, 95% 65%,
25% 115%, 5% 70%, 110% 95%, 15% 100%, -10% 85%;
}
50% {
background-position: 20% 70%, 65% 70%, 30% 70%, 80% 65%, 48% 75%, 20% 60%, 80% 60%, 45% 80%, 25% 70%, 75% 60%,
40% 85%, 30% 65%, 85% 70%, 35% 75%, 15% 70%;
}
75% {
background-position: 40% 55%, 55% 55%, 45% 60%, 65% 55%, 49% 60%, 40% 52%, 60% 52%, 48% 60%, 42% 55%, 58% 52%,
45% 62%, 43% 53%, 62% 57%, 47% 58%, 38% 55%;
}
100% {
opacity: 0;
background-position: 50% 40%, 50% 42%, 50% 38%, 50% 45%, 50% 41%, 50% 35%, 50% 43%, 50% 39%, 50% 44%, 50% 37%,
50% 46%, 50% 36%, 50% 47%, 50% 40%, 50% 38%;
}
}

View File

@ -0,0 +1,50 @@
import React, { useEffect, useRef, useState } from 'react';
import Box from '@mui/material/Box';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useTheme } from '@mui/material/styles';
import './Beta.css';
type BetaProps = {
onClick?: (event?: React.MouseEvent<HTMLElement>) => void;
}
const Beta: React.FC<BetaProps> = (props : BetaProps) => {
const { onClick } = props;
const betaRef = useRef<HTMLElement | null>(null);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const [animationKey, setAnimationKey] = useState<number>(0);
const [firstPass, setFirstPass] = useState<boolean>(true);
useEffect(() => {
// Initial animation trigger
if (firstPass && betaRef.current) {
triggerAnimation();
setFirstPass(false);
}
}, [firstPass]);
const triggerAnimation = (): void => {
if (!betaRef.current) return;
// Increment animation key to force React to recreate the element
setAnimationKey(prevKey => prevKey + 1);
// Ensure the animate class is present
betaRef.current.classList.add('animate');
};
return (
<Box className={`beta-clipper ${isMobile && "mobile"}`} onClick={(e) => { onClick && onClick(e);}}>
<Box ref={betaRef} className={`beta-label ${isMobile && "mobile"}`}>
<Box key={animationKey} className="particles"></Box>
<Box>BETA</Box>
</Box>
</Box>
);
};
export {
Beta
};

View File

@ -0,0 +1,253 @@
import React from 'react';
import {
Paper,
Box,
Container,
Grid,
Typography,
Link,
Divider,
IconButton,
Stack,
useMediaQuery,
} from '@mui/material';
import { styled, useTheme } from '@mui/material/styles';
import {
Facebook,
Twitter,
LinkedIn,
Instagram,
YouTube,
Email,
LocationOn,
Copyright,
} from '@mui/icons-material';
// Styled components
const FooterContainer = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
padding: theme.spacing(6, 0, 4),
marginTop: theme.spacing(4),
borderRadius: 0,
}));
const FooterLink = styled(Link)(({ theme }) => ({
color: theme.palette.primary.contrastText,
textDecoration: 'none',
'&:hover': {
textDecoration: 'underline',
color: theme.palette.action.active,
},
display: 'block',
marginBottom: theme.spacing(1),
}));
const FooterHeading = styled(Typography)(({ theme }) => ({
fontWeight: 600,
marginBottom: theme.spacing(2),
color: theme.palette.primary.contrastText,
}));
const ContactItem = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
marginBottom: theme.spacing(1.5),
}));
// Footer component
const Footer = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const currentYear = new Date().getFullYear();
return (
<FooterContainer elevation={0}>
<Container maxWidth="lg">
<Grid container spacing={4} justifyContent="space-between">
{/* About Company */}
<Grid size={{ xs: 12, md: 3 }}>
<Box mb={3}>
<Typography
variant="h6"
component="div"
sx={{
fontWeight: 700,
letterSpacing: '.2rem',
marginBottom: 2,
}}
>
BACKSTORY
</Typography>
<Typography variant="body2" sx={{ mb: 2 }}>
Helping candidates share their professional journey and connect with the right employers through compelling backstories.
</Typography>
<Stack direction="row">
<IconButton
size="small"
aria-label="Facebook"
sx={{
color: theme.palette.primary.contrastText,
mr: 1,
'&:hover': {
backgroundColor: 'rgba(211, 205, 191, 0.1)',
color: theme.palette.action.active,
}
}}
onClick={() => window.open('https://facebook.com/', '_blank')}
>
<Facebook />
</IconButton>
<IconButton
size="small"
aria-label="Twitter"
sx={{
color: theme.palette.primary.contrastText,
mr: 1,
'&:hover': {
backgroundColor: 'rgba(211, 205, 191, 0.1)',
color: theme.palette.action.active,
}
}}
onClick={() => window.open('https://twitter.com/', '_blank')}
>
<Twitter />
</IconButton>
<IconButton
size="small"
aria-label="LinkedIn"
sx={{
color: theme.palette.primary.contrastText,
mr: 1,
'&:hover': {
backgroundColor: 'rgba(211, 205, 191, 0.1)',
color: theme.palette.action.active,
}
}}
onClick={() => window.open('https://linkedin.com/', '_blank')}
>
<LinkedIn />
</IconButton>
<IconButton
size="small"
aria-label="Instagram"
sx={{
color: theme.palette.primary.contrastText,
mr: 1,
'&:hover': {
backgroundColor: 'rgba(211, 205, 191, 0.1)',
color: theme.palette.action.active,
}
}}
onClick={() => window.open('https://instagram.com/', '_blank')}
>
<Instagram />
</IconButton>
<IconButton
size="small"
aria-label="YouTube"
sx={{
color: theme.palette.primary.contrastText,
mr: 1,
'&:hover': {
backgroundColor: 'rgba(211, 205, 191, 0.1)',
color: theme.palette.action.active,
}
}}
onClick={() => window.open('https://youtube.com/', '_blank')}
>
<YouTube />
</IconButton>
</Stack>
</Box>
</Grid>
{/* Quick Links */}
<Grid size={{ xs: 12, sm: 6, md: 2 }}>
<FooterHeading variant="subtitle1">
For Candidates
</FooterHeading>
<FooterLink href="/create-profile">Create Profile</FooterLink>
<FooterLink href="/backstory-editor">Backstory Editor</FooterLink>
<FooterLink href="/resume-builder">Resume Builder</FooterLink>
<FooterLink href="/career-resources">Career Resources</FooterLink>
<FooterLink href="/interview-tips">Interview Tips</FooterLink>
</Grid>
{/* Quick Links */}
<Grid size={{ xs: 12, sm: 6, md: 2 }}>
<FooterHeading variant="subtitle1">
For Employers
</FooterHeading>
<FooterLink href="/post-job">Post a Job</FooterLink>
<FooterLink href="/search-candidates">Search Candidates</FooterLink>
<FooterLink href="/company-profile">Company Profile</FooterLink>
<FooterLink href="/recruiting-tools">Recruiting Tools</FooterLink>
<FooterLink href="/pricing-plans">Pricing Plans</FooterLink>
</Grid>
{/* Contact */}
<Grid size={{ xs: 12, sm: 6, md: 2 }}>
<FooterHeading variant="subtitle1">
Company
</FooterHeading>
<FooterLink href="/about-us">About Us</FooterLink>
<FooterLink href="/our-team">Our Team</FooterLink>
<FooterLink href="/blog">Blog</FooterLink>
<FooterLink href="/press">Press</FooterLink>
<FooterLink href="/careers">Careers</FooterLink>
<FooterLink href="/contact-us">Contact Us</FooterLink>
</Grid>
{/* Newsletter */}
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<ContactItem>
<Email sx={{ mr: 1, fontSize: 20 }} />
<FooterLink href="mailto:james_backstory@backstory.ketrenos.com">Email</FooterLink>
</ContactItem>
{/* <ContactItem>
<Phone sx={{ mr: 1, fontSize: 20 }} />
<FooterLink href="tel:+18005551234">+1 (800) 555-1234</FooterLink>
</ContactItem> */}
<ContactItem>
<LocationOn sx={{ mr: 1, fontSize: 20 }} />
<Typography variant="body2">
Beaverton, OR 97003
</Typography>
</ContactItem>
</Grid>
</Grid>
<Divider sx={{ my: 4, backgroundColor: 'rgba(211, 205, 191, 0.2)' }} />
{/* Bottom Footer */}
<Grid container spacing={2} alignItems="center">
<Grid size={{ xs: 12, md: 6 }}>
<Box display="flex" alignItems="center">
<Copyright sx={{ fontSize: 16, mr: 1 }} />
<Typography variant="body2">
{currentYear} James P. Ketrenos. All rights reserved.
</Typography>
</Box>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Stack
direction={isMobile ? 'column' : 'row'}
spacing={isMobile ? 1 : 3}
sx={{ textAlign: { xs: 'left', md: 'right' } }}
>
<FooterLink href="/terms" sx={{ mb: 0 }}>Terms of Service</FooterLink>
<FooterLink href="/privacy" sx={{ mb: 0 }}>Privacy Policy</FooterLink>
<FooterLink href="/accessibility" sx={{ mb: 0 }}>Accessibility</FooterLink>
<FooterLink href="/sitemap" sx={{ mb: 0 }}>Sitemap</FooterLink>
</Stack>
</Grid>
</Grid>
</Container>
</FooterContainer>
);
};
export {
Footer
};

View File

@ -0,0 +1,3 @@
.BackstoryLogo {
margin: 0px !important;
}

View File

@ -0,0 +1,426 @@
import React, { useEffect, useState } from 'react';
import { NavigateFunction, useLocation } from 'react-router-dom';
import {
AppBar,
Toolbar,
Typography,
Button,
IconButton,
Box,
Menu,
MenuItem,
ListItemIcon,
ListItemText,
Drawer,
Divider,
Avatar,
Tabs,
Tab,
Container,
Fade,
} from '@mui/material';
import { styled, useTheme } from '@mui/material/styles';
import {
Menu as MenuIcon,
Dashboard,
Person,
Logout,
Settings,
ExpandMore,
} from '@mui/icons-material';
import { NavigationLinkType } from '../BackstoryApp';
import { Beta } from './Beta';
import './Header.css';
// Interface for component props
interface HeaderProps {
isLoggedIn?: boolean;
userType?: 'candidate' | 'employer' | null;
userName?: string;
transparent?: boolean;
onLogout?: () => void;
className?: string;
navigate: NavigateFunction;
navigationLinks: NavigationLinkType[];
showLogin?: boolean;
currentPath: string;
}
// Styled components
const StyledAppBar = styled(AppBar, {
shouldForwardProp: (prop) => prop !== 'transparent',
})<{ transparent?: boolean }>(({ theme, transparent }) => ({
backgroundColor: transparent ? 'transparent' : theme.palette.primary.main,
boxShadow: transparent ? 'none' : '',
transition: 'background-color 0.3s ease',
}));
const NavLinksContainer = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'center',
flex: 1,
[theme.breakpoints.down('md')]: {
display: 'none',
},
}));
const UserActionsContainer = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
}));
const UserButton = styled(Button)(({ theme }) => ({
color: theme.palette.primary.contrastText,
textTransform: 'none',
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
padding: theme.spacing(0.5, 1.5),
borderRadius: theme.shape.borderRadius,
'&:hover': {
backgroundColor: theme.palette.action.hover,
},
}));
const MobileDrawer = styled(Drawer)(({ theme }) => ({
'& .MuiDrawer-paper': {
width: 280,
backgroundColor: theme.palette.background.paper,
},
}));
const Header: React.FC<HeaderProps> = ({
isLoggedIn = false,
userName = '',
transparent = false,
onLogout,
className,
navigate,
navigationLinks,
showLogin,
currentPath,
}) => {
const theme = useTheme();
const location = useLocation();
const BackstoryLogo = () => {
return <Typography
variant="h6"
className="BackstoryLogo"
noWrap
sx={{
cursor: "pointer",
fontWeight: 700,
letterSpacing: '.2rem',
color: theme.palette.primary.contrastText,
textDecoration: 'none',
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 1,
textTransform: "uppercase",
}}
>
<Avatar sx={{ width: 24, height: 24 }}
variant="rounded"
alt="Backstory logo"
src="/logo192.png" />
Backstory
</Typography>
};
const navLinks : NavigationLinkType[] = [
{name: "Home", path: "/", label: <BackstoryLogo/>},
...navigationLinks
];
// State for page navigation
const [ currentTab, setCurrentTab ] = useState<string>("/");
// State for mobile drawer
const [mobileOpen, setMobileOpen] = useState(false);
// State for user menu
const [userMenuAnchor, setUserMenuAnchor] = useState<null | HTMLElement>(null);
const userMenuOpen = Boolean(userMenuAnchor);
useEffect(() => {
const parts = location.pathname.split('/');
console.log(location.pathname);
let tab = '/';
if (parts.length > 1) {
tab = `/${parts[1]}`;
}
if (tab !== currentTab) {
console.log(`Setting tab to ${tab}`);
setCurrentTab(tab);
} else {
console.log(`Not setting tab to ${tab}`);
}
}, [location, currentTab]);
const handleUserMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setUserMenuAnchor(event.currentTarget);
};
const handleUserMenuClose = () => {
setUserMenuAnchor(null);
};
const handleLogout = () => {
handleUserMenuClose();
if (onLogout) {
onLogout();
}
};
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
// Render desktop navigation links
const renderNavLinks = () => {
return (
<Tabs value={currentTab} onChange={(e, newValue) => setCurrentTab(newValue)}
indicatorColor="secondary"
textColor="inherit"
variant="fullWidth"
allowScrollButtonsMobile
aria-label="Backstory navigation"
>
{navLinks.map((link) => (
<Tab
sx={{
minWidth: link.path === '/' ? "max-content" : "auto",
}}
key={link.name}
value={link.path}
label={link.label ? link.label : link.name}
onClick={() => {
navigate(link.path);
}}
/>
))}
</Tabs>
);
};
// Render mobile drawer content
const renderDrawerContent = () => {
return (
<>
<Tabs
orientation="vertical"
value={currentTab} >
{navLinks.map((link) => (
<Tab
key={link.name}
value={link.path}
label={
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{link.icon && <Box sx={{ mr: 1 }}>{link.icon}</Box>}
{link.name}
</Box>
}
onClick={(e) => { handleDrawerToggle() ; setCurrentTab(link.path); navigate(link.path);} }
/>
))}
</Tabs>
<Divider />
{!isLoggedIn && (showLogin === undefined || showLogin !== false) && (
<Box sx={{ p: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
<Button
variant="contained"
color="secondary"
fullWidth
onClick={() => navigate("/login") }
>
Login
</Button>
<Button
variant="outlined"
color="secondary"
fullWidth
onClick={() => navigate("/register") }
>
Register
</Button>
</Box>
)}
</>
);
};
// Render user account section
const renderUserSection = () => {
if (showLogin !== undefined && showLogin === false) {
return <></>;
}
if (!isLoggedIn) {
return (
<>
<Button
color="info"
variant="contained"
onClick={() => navigate("/login") }
sx={{
display: { xs: 'none', sm: 'block' },
color: theme.palette.primary.contrastText,
}}
>
Login
</Button>
<Button
color="secondary"
variant="contained"
onClick={() => navigate("/navigate") }
sx={{ display: { xs: 'none', sm: 'block' } }}
>
Register
</Button>
</>
);
}
return (
<>
<UserButton
onClick={handleUserMenuOpen}
aria-controls={userMenuOpen ? 'user-menu' : undefined}
aria-haspopup="true"
aria-expanded={userMenuOpen ? 'true' : undefined}
>
<Avatar sx={{
width: 32,
height: 32,
bgcolor: theme.palette.secondary.main,
}}>
{userName.charAt(0).toUpperCase()}
</Avatar>
<Box sx={{ display: { xs: 'none', sm: 'block' } }}>
{userName}
</Box>
<ExpandMore fontSize="small" />
</UserButton>
<Menu
id="user-menu"
anchorEl={userMenuAnchor}
open={userMenuOpen}
onClose={handleUserMenuClose}
slots={{
transition: Fade,
}}
slotProps={{
list: {
'aria-labelledby': 'user-button',
sx: {
display: 'flex',
flexDirection: 'column', // Adjusted for menu items
alignItems: 'center',
gap: '1rem',
textTransform: 'uppercase', // All caps as requested
},
},
paper: {
sx: {
minWidth: 200, // Optional: ensures reasonable menu width
},
},
}}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<MenuItem onClick={handleUserMenuClose} component="a" href="/profile">
<ListItemIcon>
<Person fontSize="small" />
</ListItemIcon>
<ListItemText>Profile</ListItemText>
</MenuItem>
<MenuItem onClick={handleUserMenuClose} component="a" href="/dashboard">
<ListItemIcon>
<Dashboard fontSize="small" />
</ListItemIcon>
<ListItemText>Dashboard</ListItemText>
</MenuItem>
<MenuItem onClick={handleUserMenuClose} component="a" href="/settings">
<ListItemIcon>
<Settings fontSize="small" />
</ListItemIcon>
<ListItemText>Settings</ListItemText>
</MenuItem>
<Divider />
<MenuItem onClick={handleLogout}>
<ListItemIcon>
<Logout fontSize="small" />
</ListItemIcon>
<ListItemText>Logout</ListItemText>
</MenuItem>
</Menu>
</>
);
};
return (
<Box>
<StyledAppBar
position="fixed"
transparent={transparent}
className={className}
>
<Container maxWidth="xl">
<Toolbar disableGutters>
{/* Logo Section */}
{/* Navigation Links - Desktop */}
<NavLinksContainer>
{renderNavLinks()}
</NavLinksContainer>
{/* User Actions Section */}
<UserActionsContainer>
{renderUserSection()}
{/* Mobile Menu Button */}
<IconButton
color="inherit"
aria-label="open drawer"
edge="end"
onClick={handleDrawerToggle}
sx={{ display: { md: 'none' } }}
>
<MenuIcon />
</IconButton>
</UserActionsContainer>
{/* Mobile Navigation Drawer */}
<MobileDrawer
variant="temporary"
anchor="right"
open={mobileOpen}
onClose={handleDrawerToggle}
ModalProps={{
keepMounted: true, // Better open performance on mobile
}}
>
{renderDrawerContent()}
</MobileDrawer>
</Toolbar>
</Container>
</StyledAppBar>
<Beta onClick={()=>{navigate('/about/beta');}}/>
</Box>
);
};
export {
Header
};

View File

@ -0,0 +1,125 @@
import React, { useState, useEffect } from 'react';
import {useNavigate, useLocation, useParams } from 'react-router-dom';
import { Box } from '@mui/material';
import { BackstoryPageProps } from '../../Components/BackstoryTab';
import { Document } from '../../Components/Document';
import { BackstoryUIOverviewPage} from './BackstoryUIOverviewPage';
import { BackstoryAppAnalysisPage } from './BackstoryAppAnalysisPage';
import { BackstoryThemeVisualizerPage } from './BackstoryThemeVisualizerPage';
import { MockupPage } from './MockupPage';
const AboutPage = (props: BackstoryPageProps) => {
const { sessionId, submitQuery, setSnack } = props;
const navigate = useNavigate();
const location = useLocation();
const { paramPage = '' } = useParams();
const [page, setPage] = useState<string>(paramPage);
/* If the location changes, set the page based on the
* second part of the path, or clear if no path */
useEffect(() => {
const parts = location.pathname.split('/');
if (parts.length > 2) {
setPage(parts[2]);
} else {
setPage('');
}
}, [location]);
const onDocumentExpand = (docName: string, open: boolean) => {
console.log("Document expanded:", { docName, open, location });
if (open) {
const parts = location.pathname.split('/');
if (parts.length > 2) {
const basePath = parts.slice(0, -1).join('/');
navigate(`${basePath}/${docName}`);
} else {
navigate(docName);
}
} else {
const basePath = location.pathname.split('/').slice(0, -1).join('/');
navigate(`${basePath}`);
}
}
return (<Box sx={{gap: 1}}>
<Document {...{
title: "About",
filepath: "/docs/about.md",
onExpand: (open: boolean) => { onDocumentExpand('about', open); },
expanded: page === 'about',
sessionId,
submitQuery: submitQuery,
setSnack,
}} />
<Document {...{
title: "BETA",
filepath: "/docs/beta.md",
onExpand: (open: boolean) => { onDocumentExpand('beta', open); },
expanded: page === 'beta',
sessionId,
submitQuery: submitQuery,
setSnack,
}} />
<Document {...{
title: "Resume Generation Architecture",
filepath: "/docs/resume-generation.md",
onExpand: (open: boolean) => { onDocumentExpand('resume-generation', open); },
expanded: page === 'resume-generation',
sessionId,
submitQuery: submitQuery,
setSnack,
}} />
<Document {...{
title: "Application Architecture",
filepath: "/docs/about-app.md",
onExpand: (open: boolean) => { onDocumentExpand('about-app', open); },
expanded: page === 'about-app',
sessionId,
submitQuery: submitQuery,
setSnack,
}} />
<Document {...{
title: "UI Overview",
children: <BackstoryUIOverviewPage/>,
onExpand: (open: boolean) => { onDocumentExpand('ui-overview', open); },
expanded: page === 'ui-overview',
sessionId,
submitQuery: submitQuery,
setSnack,
}} />
<Document {...{
title: "Theme Visualizer",
onExpand: (open: boolean) => { onDocumentExpand('theme-visualizer', open); },
expanded: page === 'theme-visualizer',
children: <BackstoryThemeVisualizerPage/>,
sessionId,
submitQuery: submitQuery,
setSnack,
}} />
<Document {...{
title: "App Analysis",
onExpand: (open: boolean) => { onDocumentExpand('app-analysis', open); },
expanded: page === 'app-analysis',
children: <BackstoryAppAnalysisPage/>,
sessionId,
submitQuery: submitQuery,
setSnack,
}} />
<Document {...{
title: "UI Mockup",
onExpand: (open: boolean) => { onDocumentExpand('ui-mockup', open); },
expanded: page === 'ui-mockup',
children: <MockupPage />,
sessionId,
submitQuery: submitQuery,
setSnack,
}} />
</Box>)
};
export {
AboutPage
};

View File

@ -0,0 +1,312 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { BrowserRouter } from 'react-router-dom';
import { Box, Typography, Paper, Container } from '@mui/material';
// Import the backstoryTheme
// BackstoryAnalysisDisplay component
const BackstoryAppAnalysisPage = () => {
return (
<Box sx={{ backgroundColor: 'background.default', minHeight: '100vh', py: 4 }}>
<Container maxWidth="lg">
<Paper sx={{ p: 4, boxShadow: 2 }}>
<Typography variant="h1" component="h1" sx={{ mb: 3, color: 'primary.main' }}>
Backstory Application Analysis
</Typography>
<Typography variant="h2" component="h2">
Core Concept
</Typography>
<Typography variant="body1">
Backstory is a dual-purpose platform designed to bridge the gap between job candidates and
employers/recruiters with an AI-powered approach to professional profiles and resume generation.
</Typography>
<Typography variant="h3" component="h3" sx={{ mt: 3 }}>
Primary User Types
</Typography>
<Box component="ol" sx={{ pl: 4 }}>
<li>
<Typography variant="body1" component="div">
<strong>Job Candidates</strong> - Upload and manage comprehensive professional histories
and generate tailored resumes for specific positions
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Employers/Recruiters</strong> - Search for candidates, directly interact with AI
assistants about candidate experiences, and generate position-specific resumes
</Typography>
</li>
</Box>
<Typography variant="h2" component="h2" sx={{ mt: 4 }}>
Key Features
</Typography>
<Typography variant="h3" component="h3" sx={{ mt: 3 }}>
For Candidates
</Typography>
<Box component="ul" sx={{ pl: 4 }}>
<li>
<Typography variant="body1" component="div">
<strong>Complete Profile Management</strong> - Create detailed professional histories beyond typical resume constraints
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>AI-Assisted Q&A Setup</strong> - Configure an AI assistant to answer employer questions about your experience
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Smart Resume Generator</strong> - Create tailored resumes for specific positions using AI
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Analytics Dashboard</strong> - Track profile views, resume downloads, and employer engagement
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Privacy Controls</strong> - Manage visibility and access to your professional information
</Typography>
</li>
</Box>
<Typography variant="h3" component="h3" sx={{ mt: 3 }}>
For Employers
</Typography>
<Box component="ul" sx={{ pl: 4 }}>
<li>
<Typography variant="body1" component="div">
<strong>Advanced Candidate Search</strong> - Find candidates with specific skills, experience levels, and qualifications
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Interactive Q&A</strong> - Ask questions directly to candidate AI assistants to learn more about their experience
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Resume Generation</strong> - Generate candidate resumes tailored to specific job requirements
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Talent Pool Management</strong> - Create and manage groups of candidates for different positions
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Job Posting Management</strong> - Create, manage, and track applications for job postings
</Typography>
</li>
</Box>
<Typography variant="h2" component="h2" sx={{ mt: 4 }}>
Design Analysis
</Typography>
<Typography variant="h3" component="h3" sx={{ mt: 3 }}>
Navigation Structure
</Typography>
<Typography variant="body1">
The application uses a role-based navigation system with:
</Typography>
<Box component="ul" sx={{ pl: 4 }}>
<li>
<Typography variant="body1" component="div">
<strong>Public Navigation</strong> - Home, About, Pricing, Login/Register accessible to all users
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Candidate Dashboard Navigation</strong> - Profile, Backstory, Resumes, Q&A Setup, Analytics, Settings
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Employer Dashboard Navigation</strong> - Dashboard, Search, Saved, Jobs, Company, Analytics, Settings
</Typography>
</li>
</Box>
<Typography variant="h3" component="h3" sx={{ mt: 3 }}>
UI Patterns
</Typography>
<Box component="ol" sx={{ pl: 4 }}>
<li>
<Typography variant="body1" component="div">
<strong>Dashboard Cards</strong> - Both user types have dashboards with card-based information displays
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Tab-Based Content Organization</strong> - Many screens use horizontal tabs to organize related content
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Form-Based Editors</strong> - Profile and content editors use structured forms with varied input types
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Three-Column Layouts</strong> - Many screens follow a left sidebar, main content, right sidebar pattern
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Preview/Edit Toggle</strong> - Resume and profile editing screens offer both editing and preview modes
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Filter-Based Search</strong> - Employer search uses multiple filter categories to refine candidate results
</Typography>
</li>
</Box>
<Typography variant="h3" component="h3" sx={{ mt: 3 }}>
Mobile Adaptations
</Typography>
<Typography variant="body1">
The mobile designs show a simplified navigation structure with bottom tabs and a hamburger menu,
maintaining the core functionality while adapting to smaller screens.
</Typography>
<Typography variant="h2" component="h2" sx={{ mt: 4 }}>
Technology Integration
</Typography>
<Typography variant="h3" component="h3" sx={{ mt: 3 }}>
AI Components
</Typography>
<Box component="ul" sx={{ pl: 4 }}>
<li>
<Typography variant="body1" component="div">
<strong>LLM Integration</strong> - Supports multiple AI models (Claude, GPT-4, self-hosted models)
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Candidate AI Assistant</strong> - Personalized AI chatbot that answers questions about candidate experience
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Resume Generation</strong> - AI-powered resume creation based on job requirements
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Skills Matching</strong> - Automated matching between candidate skills and job requirements
</Typography>
</li>
</Box>
<Typography variant="h3" component="h3" sx={{ mt: 3 }}>
External Integrations
</Typography>
<Box component="ul" sx={{ pl: 4 }}>
<li>
<Typography variant="body1" component="div">
<strong>Authentication</strong> - OAuth with Google, LinkedIn, GitHub
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Data Import</strong> - LinkedIn profile import, resume parsing (PDF, DOCX), CSV/JSON import
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>ATS Compatibility</strong> - Integration with employer Applicant Tracking Systems
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Vector Databases</strong> - Semantic search capabilities for candidate matching
</Typography>
</li>
</Box>
<Typography variant="h2" component="h2" sx={{ mt: 4 }}>
Key Differentiators
</Typography>
<Box component="ol" sx={{ pl: 4 }}>
<li>
<Typography variant="body1" component="div">
<strong>Beyond the Resume</strong> - Focuses on comprehensive professional stories rather than just resume highlights
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>AI-Mediated Communication</strong> - Uses AI to facilitate deeper understanding of candidate experiences
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Bidirectional Resume Generation</strong> - Both candidates and employers can generate tailored resumes
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Configurable AI Personalities</strong> - Candidates can customize how their AI assistant responds to questions
</Typography>
</li>
<li>
<Typography variant="body1" component="div">
<strong>Deep Analytics</strong> - Both candidates and employers receive insights about their engagement
</Typography>
</li>
</Box>
<Typography variant="h2" component="h2" sx={{ mt: 4 }}>
Deployment Options
</Typography>
<Typography variant="body1">
The system supports multiple deployment architectures:
</Typography>
<Box component="ul" sx={{ pl: 4 }}>
<li>
<Typography variant="body1">Self-hosted (on-premises)</Typography>
</li>
<li>
<Typography variant="body1">Cloud-hosted (SaaS model)</Typography>
</li>
<li>
<Typography variant="body1">Hybrid deployment (mixed cloud/on-premises)</Typography>
</li>
</Box>
<Typography variant="h2" component="h2" sx={{ mt: 4 }}>
Security and Privacy Considerations
</Typography>
<Box component="ul" sx={{ pl: 4 }}>
<li>
<Typography variant="body1">Granular candidate privacy controls</Typography>
</li>
<li>
<Typography variant="body1">Role-based access for employer teams</Typography>
</li>
<li>
<Typography variant="body1">Data management options for compliance requirements</Typography>
</li>
</Box>
</Paper>
</Container>
</Box>
);
};
export {
BackstoryAppAnalysisPage
}

View File

@ -0,0 +1,196 @@
import React from 'react';
import { backstoryTheme } from '../BackstoryTheme';
// This component provides a visual demonstration of the theme colors
const BackstoryThemeVisualizerPage = () => {
const colorSwatch = (color: string, name: string, textColor = '#fff') => (
<div className="flex flex-col items-center">
<div
className="w-20 h-20 rounded-lg shadow-md flex items-center justify-center mb-2"
style={{ backgroundColor: color, color: textColor }}>
{name}
</div>
<span className="text-xs">{color}</span>
</div>
);
return (
<div className="p-8">
<h1 className="text-2xl font-bold mb-6" style={{ color: backstoryTheme.palette.text.primary }}>
Backstory Theme Visualization
</h1>
<div className="mb-8">
<h2 className="text-xl mb-4" style={{ color: backstoryTheme.palette.text.primary }}>
Primary Colors
</h2>
<div className="flex space-x-4">
{colorSwatch(backstoryTheme.palette.primary.main, 'Primary', backstoryTheme.palette.primary.contrastText)}
{colorSwatch(backstoryTheme.palette.secondary.main, 'Secondary', backstoryTheme.palette.secondary.contrastText)}
{colorSwatch(backstoryTheme.palette.custom.highlight, 'Highlight', '#fff')}
</div>
</div>
<div className="mb-8">
<h2 className="text-xl mb-4" style={{ color: backstoryTheme.palette.text.primary }}>
Background Colors
</h2>
<div className="flex space-x-4">
{colorSwatch(backstoryTheme.palette.background.default, 'Default', '#000')}
{colorSwatch(backstoryTheme.palette.background.paper, 'Paper', '#000')}
</div>
</div>
<div className="mb-8">
<h2 className="text-xl mb-4" style={{ color: backstoryTheme.palette.text.primary }}>
Text Colors
</h2>
<div className="flex space-x-4">
{colorSwatch(backstoryTheme.palette.text.primary, 'Primary', '#fff')}
{colorSwatch(backstoryTheme.palette.text.secondary, 'Secondary', '#fff')}
</div>
</div>
<div className="mb-8 border p-6 rounded-lg" style={{ backgroundColor: backstoryTheme.palette.background.paper }}>
<h2 className="text-xl mb-4" style={{ color: backstoryTheme.palette.text.primary }}>
Typography Examples
</h2>
<div className="mb-4">
<h1 style={{
fontFamily: backstoryTheme.typography.fontFamily,
fontSize: backstoryTheme.typography.h1.fontSize,
fontWeight: backstoryTheme.typography.h1.fontWeight,
color: backstoryTheme.typography.h1.color,
}}>
Heading 1 - Backstory Application
</h1>
</div>
<div className="mb-4">
<p style={{
fontFamily: backstoryTheme.typography.fontFamily,
fontSize: backstoryTheme.typography.body1.fontSize,
color: backstoryTheme.typography.body1.color,
}}>
Body Text - This is how the regular text content will appear in the Backstory application.
The application uses Roboto as its primary font family, with carefully selected sizing and colors.
</p>
</div>
{/* <div className="mt-6">
<a href="#" style={{
color: backstoryTheme.components?.MuiLink?.styleOverrides.root.color || "inherit",
textDecoration: backstoryTheme.components.MuiLink.styleOverrides.root.textDecoration,
}}>
This is how links will appear by default
</a>
</div> */}
</div>
<div className="mb-8">
<h2 className="text-xl mb-4" style={{ color: backstoryTheme.palette.text.primary }}>
UI Component Examples
</h2>
<div className="p-4 mb-4 rounded-lg" style={{ backgroundColor: backstoryTheme.palette.background.paper }}>
<div className="p-2 mb-4 rounded" style={{ backgroundColor: backstoryTheme.palette.primary.main }}>
<span style={{ color: backstoryTheme.palette.primary.contrastText }}>
AppBar Background
</span>
</div>
<div style={{
padding: '8px 16px',
backgroundColor: backstoryTheme.palette.primary.main,
color: backstoryTheme.palette.primary.contrastText,
display: 'inline-block',
borderRadius: '4px',
cursor: 'pointer',
fontFamily: backstoryTheme.typography.fontFamily,
}}>
Primary Button
</div>
<div className="mt-4" style={{
padding: '8px 16px',
backgroundColor: backstoryTheme.palette.secondary.main,
color: backstoryTheme.palette.secondary.contrastText,
display: 'inline-block',
borderRadius: '4px',
cursor: 'pointer',
fontFamily: backstoryTheme.typography.fontFamily,
}}>
Secondary Button
</div>
<div className="mt-4" style={{
padding: '8px 16px',
backgroundColor: backstoryTheme.palette.action.active,
color: '#fff',
display: 'inline-block',
borderRadius: '4px',
cursor: 'pointer',
fontFamily: backstoryTheme.typography.fontFamily,
}}>
Action Button
</div>
</div>
</div>
<div>
<h2 className="text-xl mb-4" style={{ color: backstoryTheme.palette.text.primary }}>
Theme Color Breakdown
</h2>
<table className="border-collapse">
<thead>
<tr>
<th className="border p-2 text-left"
style={{ backgroundColor: backstoryTheme.palette.background.default, color: backstoryTheme.palette.text.primary }}>Color Name</th>
<th className="border p-2 text-left"
style={{ backgroundColor: backstoryTheme.palette.background.default, color: backstoryTheme.palette.text.primary }}>Hex Value</th>
<th className="border p-2 text-left"
style={{ backgroundColor: backstoryTheme.palette.background.default, color: backstoryTheme.palette.text.primary }}>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>Primary Main</td>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>{backstoryTheme.palette.primary.main}</td>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>Midnight Blue - Used for main headers and primary UI elements</td>
</tr>
<tr>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>Primary Contrast</td>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>{backstoryTheme.palette.primary.contrastText}</td>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>Warm Gray - Text that appears on primary color backgrounds</td>
</tr>
<tr>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>Secondary Main</td>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>{backstoryTheme.palette.secondary.main}</td>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>Dusty Teal - Used for secondary actions and accents</td>
</tr>
<tr>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>Highlight</td>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>{backstoryTheme.palette.custom.highlight}</td>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>Golden Ochre - Used for highlights, accents, and important actions</td>
</tr>
<tr>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>Background Default</td>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>{backstoryTheme.palette.background.default}</td>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>Warm Gray - Main background color for the application</td>
</tr>
<tr>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>Text Primary</td>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>{backstoryTheme.palette.text.primary}</td>
<td className="border p-2" style={{ color: backstoryTheme.palette.text.primary }}>Charcoal Black - Primary text color throughout the app</td>
</tr>
</tbody>
</table>
</div>
</div>
);
};
export {
BackstoryThemeVisualizerPage
};

View File

@ -0,0 +1,361 @@
import React from 'react';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { Box, Container, Paper, Typography, Grid, Divider, CssBaseline } from '@mui/material';
import { backstoryTheme } from 'BackstoryTheme';
const BackstoryUIOverviewPage: React.FC = () => {
return (
<ThemeProvider theme={backstoryTheme}>
<CssBaseline />
<Box sx={{ bgcolor: 'background.default', overflow: "hidden", py: 4 }}>
<Container maxWidth="lg">
<Paper sx={{ p: 4, borderRadius: 2, boxShadow: 2 }}>
{/* Header */}
<Box sx={{ p: 3, bgcolor: 'background.paper', borderRadius: 2, mb: 4, boxShadow: 1 }}>
<Typography variant="h4" component="h1" sx={{ fontWeight: 'bold', color: 'primary.main', mb: 1 }}>
Backstory UI Architecture
</Typography>
<Typography variant="body1" color="text.secondary">
A visual overview of the dual-purpose application serving candidates and employers
</Typography>
</Box>
{/* User Types */}
<Grid container spacing={3} sx={{ mb: 4 }}>
<Grid size={{xs: 12, md: 6}}>
<Box sx={{
p: 3,
bgcolor: 'rgba(74, 122, 125, 0.1)',
borderRadius: 2,
border: '1px solid',
borderColor: 'rgba(74, 122, 125, 0.3)',
height: '100%'
}}>
<Typography variant="h6" sx={{ color: 'secondary.main', mb: 2, fontWeight: 'bold' }}>
Candidate Experience
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
{[
'Create comprehensive professional profiles',
'Configure AI assistant for employer Q&A',
'Generate tailored resumes for specific jobs',
'Track profile engagement metrics'
].map((item, index) => (
<Box key={index} sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Box sx={{ width: 8, height: 8, borderRadius: '50%', bgcolor: 'secondary.main' }} />
<Typography variant="body2">{item}</Typography>
</Box>
))}
</Box>
</Box>
</Grid>
<Grid size={{ xs: 12, md: 6}}>
<Box sx={{
p: 3,
bgcolor: 'rgba(26, 37, 54, 0.1)',
borderRadius: 2,
border: '1px solid',
borderColor: 'rgba(26, 37, 54, 0.3)',
height: '100%'
}}>
<Typography variant="h6" sx={{ color: 'primary.main', mb: 2, fontWeight: 'bold' }}>
Employer Experience
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
{[
'Search for candidates with specific skills',
'Interact with candidate AI assistants',
'Generate position-specific candidate resumes',
'Manage talent pools and job listings'
].map((item, index) => (
<Box key={index} sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Box sx={{ width: 8, height: 8, borderRadius: '50%', bgcolor: 'primary.main' }} />
<Typography variant="body2">{item}</Typography>
</Box>
))}
</Box>
</Box>
</Grid>
</Grid>
{/* UI Components */}
<Box sx={{ p: 3, bgcolor: 'background.paper', borderRadius: 2, mb: 4, boxShadow: 1 }}>
<Typography variant="h5" sx={{ color: 'text.primary', mb: 3, fontWeight: 'bold' }}>
Key UI Components
</Typography>
<Grid container spacing={2}>
{[
{ title: 'Dashboards', description: 'Role-specific dashboards with card-based metrics and action items' },
{ title: 'Profile Editors', description: 'Comprehensive forms for managing professional information' },
{ title: 'Resume Builder', description: 'AI-powered tools for creating tailored resumes' },
{ title: 'Q&A Interface', description: 'Chat-like interface for employer-candidate AI interaction' },
{ title: 'Search & Filters', description: 'Advanced search with multiple filter categories' },
{ title: 'Analytics Dashboards', description: 'Visual metrics for tracking engagement and performance' }
].map((component, index) => (
<Grid size={{xs: 12, sm: 6, md: 4}} key={index}>
<Box sx={{
p: 2,
border: '1px solid',
borderColor: 'divider',
borderRadius: 1,
height: '100%',
transition: 'all 0.2s ease-in-out',
'&:hover': {
bgcolor: 'rgba(212, 160, 23, 0.05)',
borderColor: 'action.active',
transform: 'translateY(-2px)',
boxShadow: 1
}
}}>
<Typography variant="h6" sx={{ color: 'secondary.main', mb: 1, fontWeight: 'medium' }}>
{component.title}
</Typography>
<Typography variant="body2" color="text.secondary">
{component.description}
</Typography>
</Box>
</Grid>
))}
</Grid>
</Box>
{/* Navigation Structure */}
<Grid container spacing={3} sx={{ mb: 4 }}>
{[
{
title: 'Candidate Navigation',
items: ['Dashboard', 'Profile', 'Backstory', 'Resumes', 'Q&A Setup', 'Analytics', 'Settings'],
color: 'secondary.main',
borderColor: 'secondary.main'
},
{
title: 'Employer Navigation',
items: ['Dashboard', 'Search', 'Saved', 'Jobs', 'Company', 'Analytics', 'Settings'],
color: 'primary.main',
borderColor: 'primary.main'
},
{
title: 'Public Navigation',
items: ['Home', 'About', 'Pricing', 'Login', 'Register'],
color: 'custom.highlight',
borderColor: 'custom.highlight'
}
].map((nav, index) => (
<Grid size={{xs:12, md:4}} key={index}>
<Box sx={{
p: 3,
bgcolor: 'background.paper',
borderRadius: 2,
boxShadow: 1,
height: '100%'
}}>
<Typography variant="h6" sx={{ color: 'text.primary', mb: 2, fontWeight: 'bold' }}>
{nav.title}
</Typography>
<Box sx={{
borderLeft: 3,
borderColor: nav.borderColor,
pl: 2,
py: 1,
display: 'flex',
flexDirection: 'column',
gap: 1.5
}}>
{nav.items.map((item, idx) => (
<Typography key={idx} sx={{ color: nav.color, fontWeight: 'medium' }}>
{item}
</Typography>
))}
</Box>
</Box>
</Grid>
))}
</Grid>
{/* Connection Points */}
<Box sx={{ p: 3, bgcolor: 'background.paper', borderRadius: 2, mb: 4, boxShadow: 1 }}>
<Typography variant="h5" sx={{ color: 'text.primary', mb: 3, fontWeight: 'bold' }}>
System Connection Points
</Typography>
<Box sx={{ position: 'relative', py: 2 }}>
{/* Connection line */}
<Box sx={{
position: 'absolute',
left: '50%',
top: 0,
bottom: 0,
width: 1,
borderColor: 'divider',
zIndex: 0,
borderLeft: "1px solid",
overflow: "hidden",
}} />
{/* Connection points */}
{[
{ left: 'Candidate Profile', right: 'Employer Search' },
{ left: 'Q&A Setup', right: 'Q&A Interface' },
{ left: 'Resume Generator', right: 'Job Posts' }
].map((connection, index) => (
<Box
key={index}
sx={{
display: 'flex',
alignItems: 'center',
mb: index < 2 ? 5 : 0,
position: 'relative',
zIndex: 1,
}}
>
<Box sx={{
flex: 1,
display: 'flex',
justifyContent: 'flex-end',
pr: 3
}}>
<Box sx={{
display: 'inline-block',
bgcolor: 'rgba(74, 122, 125, 0.1)',
p: 2,
borderRadius: 2,
color: 'secondary.main',
fontWeight: 'medium',
border: '1px solid',
borderColor: 'rgba(74, 122, 125, 0.3)'
}}>
{connection.left}
</Box>
</Box>
<Box sx={{
width: 16,
height: 16,
borderRadius: '50%',
bgcolor: 'custom.highlight',
zIndex: 2,
boxShadow: 2,
}} />
<Box sx={{
flex: 1,
pl: 3,
}}>
<Box sx={{
display: 'inline-block',
bgcolor: 'rgba(26, 37, 54, 0.1)',
p: 2,
borderRadius: 2,
color: 'primary.main',
fontWeight: 'medium',
border: '1px solid',
borderColor: 'rgba(26, 37, 54, 0.3)',
}}>
{connection.right}
</Box>
</Box>
</Box>
))}
</Box>
</Box>
{/* Mobile Adaptation */}
<Box sx={{ p: 3, bgcolor: 'background.paper', borderRadius: 2, boxShadow: 1 }}>
<Typography variant="h5" sx={{ color: 'text.primary', mb: 3, fontWeight: 'bold' }}>
Mobile Adaptation
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<Box sx={{
width: 200,
height: 400,
border: '4px solid',
borderColor: 'text.primary',
borderRadius: 5,
p: 1,
bgcolor: 'background.default'
}}>
<Box sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
border: '1px solid',
borderColor: 'divider',
borderRadius: 4,
overflow: 'hidden'
}}>
{/* Mobile header */}
<Box sx={{
bgcolor: 'primary.main',
color: 'primary.contrastText',
p: 1,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<Typography sx={{ fontWeight: 'bold', fontSize: '0.875rem' }}>BACKSTORY</Typography>
<Box></Box>
</Box>
{/* Mobile content */}
<Box sx={{
flex: 1,
p: 1.5,
overflow: 'auto',
fontSize: '0.75rem'
}}>
<Typography sx={{ mb: 1, fontWeight: 'medium' }}>Welcome back, [Name]!</Typography>
<Typography sx={{ fontSize: '0.675rem', mb: 2 }}>Profile: 75% complete</Typography>
<Box sx={{
border: '1px solid',
borderColor: 'divider',
borderRadius: 1,
p: 1.5,
mb: 2,
bgcolor: 'background.paper'
}}>
<Typography sx={{ fontWeight: 'bold', fontSize: '0.75rem', mb: 0.5 }}>Resume Builder</Typography>
<Typography sx={{ fontSize: '0.675rem' }}>3 custom resumes</Typography>
</Box>
<Box sx={{
border: '1px solid',
borderColor: 'divider',
borderRadius: 1,
p: 1.5,
bgcolor: 'background.paper'
}}>
<Typography sx={{ fontWeight: 'bold', fontSize: '0.75rem', mb: 0.5 }}>Recent Activity</Typography>
<Typography sx={{ fontSize: '0.675rem' }}> 5 profile views</Typography>
<Typography sx={{ fontSize: '0.675rem' }}> 2 downloads</Typography>
</Box>
</Box>
{/* Mobile footer */}
<Box sx={{
bgcolor: 'background.default',
p: 1,
display: 'flex',
justifyContent: 'space-around',
borderTop: '1px solid',
borderColor: 'divider'
}}>
<Typography sx={{ fontWeight: 'bold', fontSize: '0.75rem', color: 'secondary.main' }}>Home</Typography>
<Typography sx={{ fontSize: '0.75rem', color: 'text.secondary' }}>Profile</Typography>
<Typography sx={{ fontSize: '0.75rem', color: 'text.secondary' }}>More</Typography>
</Box>
</Box>
</Box>
</Box>
</Box>
</Paper>
</Container>
</Box>
</ThemeProvider>
);
};
export {
BackstoryUIOverviewPage
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 800 500" xmlns="http://www.w3.org/2000/svg">
<!-- Background gradient -->
<defs>
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#1a2b45;stop-opacity:1" />
<stop offset="100%" style="stop-color:#2a3b55;stop-opacity:1" />
</linearGradient>
<!-- Shadow filter -->
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="3" dy="3" stdDeviation="5" flood-opacity="0.3"/>
</filter>
</defs>
<!-- Base background -->
<rect width="800" height="500" fill="url(#bgGradient)" rx="5" ry="5"/>
<!-- Abstract connection lines in background -->
<path d="M100,100 C300,50 500,200 700,100" stroke="#ffffff" stroke-width="1" fill="none" stroke-opacity="0.1"/>
<path d="M100,200 C300,150 500,300 700,200" stroke="#ffffff" stroke-width="1" fill="none" stroke-opacity="0.1"/>
<path d="M100,300 C300,250 500,400 700,300" stroke="#ffffff" stroke-width="1" fill="none" stroke-opacity="0.1"/>
<path d="M100,400 C300,350 500,450 700,400" stroke="#ffffff" stroke-width="1" fill="none" stroke-opacity="0.1"/>
<!-- Left person - more photorealistic style -->
<g filter="url(#shadow)">
<!-- Suit/blazer shape -->
<path d="M190,180 L230,170 Q260,230 250,300 L200,320 Q190,250 170,230 Z" fill="#2c3e50"/>
<!-- Shirt collar -->
<path d="M200,175 L230,170 L235,190 L210,195 Z" fill="#f5f5f5"/>
<!-- Head shape -->
<circle cx="210" cy="130" r="50" fill="#e0c4a8"/>
<!-- Hair -->
<path d="M170,115 Q210,80 250,115 L240,135 Q215,110 180,135 Z" fill="#4a3520"/>
<!-- Face features suggestion -->
<ellipse cx="195" cy="120" rx="5" ry="3" fill="#333333"/>
<ellipse cx="225" cy="120" rx="5" ry="3" fill="#333333"/>
<path d="M195,145 Q210,155 225,145" fill="none" stroke="#333333" stroke-width="2"/>
</g>
<!-- Middle elements - digital content -->
<g filter="url(#shadow)">
<!-- Resume/CV element -->
<rect x="310" y="150" width="180" height="240" rx="5" ry="5" fill="#f5f5f5"/>
<!-- Resume content suggestion -->
<line x1="330" y1="180" x2="470" y2="180" stroke="#333" stroke-width="3"/>
<line x1="330" y1="200" x2="470" y2="200" stroke="#333" stroke-width="1"/>
<line x1="330" y1="215" x2="470" y2="215" stroke="#333" stroke-width="1"/>
<line x1="330" y1="230" x2="470" y2="230" stroke="#333" stroke-width="1"/>
<line x1="330" y1="260" x2="390" y2="260" stroke="#333" stroke-width="2"/>
<line x1="330" y1="280" x2="470" y2="280" stroke="#333" stroke-width="1"/>
<line x1="330" y1="295" x2="470" y2="295" stroke="#333" stroke-width="1"/>
<line x1="330" y1="310" x2="470" y2="310" stroke="#333" stroke-width="1"/>
<line x1="330" y1="340" x2="390" y2="340" stroke="#333" stroke-width="2"/>
<line x1="330" y1="360" x2="470" y2="360" stroke="#333" stroke-width="1"/>
</g>
<!-- Digital connecting elements -->
<g>
<path d="M250,200 Q275,210 310,210" stroke="#4f97eb" stroke-width="2" fill="none" stroke-dasharray="3,3"/>
<circle cx="250" cy="200" r="5" fill="#4f97eb"/>
<circle cx="310" cy="210" r="5" fill="#4f97eb"/>
<path d="M250,250 Q285,240 310,260" stroke="#4f97eb" stroke-width="2" fill="none" stroke-dasharray="3,3"/>
<circle cx="250" cy="250" r="5" fill="#4f97eb"/>
<circle cx="310" cy="260" r="5" fill="#4f97eb"/>
<path d="M250,300 Q275,320 310,310" stroke="#4f97eb" stroke-width="2" fill="none" stroke-dasharray="3,3"/>
<circle cx="250" cy="300" r="5" fill="#4f97eb"/>
<circle cx="310" cy="310" r="5" fill="#4f97eb"/>
<path d="M490,200 Q515,210 550,190" stroke="#4f97eb" stroke-width="2" fill="none" stroke-dasharray="3,3"/>
<circle cx="490" cy="200" r="5" fill="#4f97eb"/>
<circle cx="550" cy="190" r="5" fill="#4f97eb"/>
<path d="M490,250 Q515,240 550,260" stroke="#4f97eb" stroke-width="2" fill="none" stroke-dasharray="3,3"/>
<circle cx="490" cy="250" r="5" fill="#4f97eb"/>
<circle cx="550" cy="260" r="5" fill="#4f97eb"/>
<path d="M490,300 Q515,320 550,310" stroke="#4f97eb" stroke-width="2" fill="none" stroke-dasharray="3,3"/>
<circle cx="490" cy="300" r="5" fill="#4f97eb"/>
<circle cx="550" cy="310" r="5" fill="#4f97eb"/>
</g>
<!-- Right person - more photorealistic style -->
<g filter="url(#shadow)">
<!-- Suit/blazer shape -->
<path d="M570,180 L610,170 Q630,230 620,300 L580,320 Q560,250 550,230 Z" fill="#2c3e50"/>
<!-- Shirt collar -->
<path d="M580,175 L610,170 L615,190 L590,195 Z" fill="#f5f5f5"/>
<!-- Head shape -->
<circle cx="590" cy="130" r="50" fill="#e0c4a8"/>
<!-- Hair -->
<path d="M550,110 Q590,80 625,110 L615,140 Q585,120 560,140 Z" fill="#774936"/>
<!-- Face features suggestion -->
<ellipse cx="575" cy="120" rx="5" ry="3" fill="#333333"/>
<ellipse cx="605" cy="120" rx="5" ry="3" fill="#333333"/>
<path d="M575,145 Q590,155 605,145" fill="none" stroke="#333333" stroke-width="2"/>
</g>
<!-- Top title and subtitle elements -->
<g>
<text x="400" y="60" font-family="Arial, sans-serif" font-size="24" text-anchor="middle" fill="#ffffff" font-weight="bold">Professional Conversations</text>
<text x="400" y="90" font-family="Arial, sans-serif" font-size="16" text-anchor="middle" fill="#e1e8f0">Discover the depth of your career journey</text>
</g>
<!-- Additional decorative elements -->
<circle cx="150" cy="420" r="30" fill="#3b5998" opacity="0.2"/>
<circle cx="650" cy="420" r="30" fill="#3b5998" opacity="0.2"/>
<circle cx="400" cy="450" r="20" fill="#3b5998" opacity="0.2"/>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,492 @@
import React from 'react';
import {
Box,
Button,
Container,
Paper,
Typography,
Stack,
Card,
CardContent,
CardActions,
} from '@mui/material';
import { styled } from '@mui/material/styles';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import PersonSearchIcon from '@mui/icons-material/PersonSearch';
import WorkHistoryIcon from '@mui/icons-material/WorkHistory';
import QuestionAnswerIcon from '@mui/icons-material/QuestionAnswer';
import DescriptionIcon from '@mui/icons-material/Description';
import professionalConversationPng from './Conversation.png';
// Placeholder for Testimonials component
const Testimonials = () => {
return (
<Paper elevation={0} sx={{ p: 2, my: 4 }}>
Testimonials Component (Quotes, Success Stories)
</Paper>
);
};
// Styled components
const HeroSection = styled(Box)(({ theme }) => ({
padding: theme.spacing(8, 0),
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
[theme.breakpoints.down('md')]: {
padding: theme.spacing(6, 0),
},
}));
const HeroButton = styled(Button)(({ theme }) => ({
marginTop: theme.spacing(2),
padding: theme.spacing(1, 3),
fontWeight: 500,
backgroundColor: theme.palette.action.active,
color: theme.palette.background.paper,
'&:hover': {
backgroundColor: theme.palette.action.active,
opacity: 0.9,
},
}));
const FeatureIcon = styled(Box)(({ theme }) => ({
backgroundColor: theme.palette.action.active,
color: theme.palette.background.paper,
borderRadius: '50%',
padding: theme.spacing(2),
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginBottom: theme.spacing(2),
width: 64,
height: 64,
}));
// Feature Card Component
const FeatureCard = ({
icon,
title,
description
}: {
icon: React.ReactNode;
title: string;
description: string;
}) => {
return (
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
<CardContent sx={{ flexGrow: 1 }}>
<Box display="flex" justifyContent="center" mb={2}>
{icon}
</Box>
<Typography variant="h5" component="h3" gutterBottom align="center">
{title}
</Typography>
<Typography variant="body1" color="text.secondary">
{description}
</Typography>
</CardContent>
<CardActions sx={{ justifyContent: 'center', pb: 2 }}>
<Button size="small" endIcon={<ArrowForwardIcon />}>
Learn more
</Button>
</CardActions>
</Card>
);
};
const HomePage = () => {
return (<Box sx={{display: "flex", flexDirection: "column"}}>
{/* Hero Section */}
<HeroSection>
<Container>
<Box sx={{
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
gap: 4,
alignItems: 'center',
flexGrow: 1,
maxWidth: "1024px"
}}>
<Box sx={{ flex: 1, flexGrow: 1 }}>
<Typography
variant="h2"
component="h1"
sx={{
fontWeight: 700,
fontSize: { xs: '2rem', md: '3rem' },
mb: 2
}}
>
Your complete professional story, beyond a single page
</Typography>
<Typography variant="h5" sx={{ mb: 3, fontWeight: 400 }}>
Let potential employers discover the depth of your experience through interactive Q&A and tailored resumes
</Typography>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
<HeroButton
variant="contained"
size="large"
>
Get Started as Candidate
</HeroButton>
<HeroButton
variant="outlined"
size="large"
sx={{
backgroundColor: 'transparent',
border: '2px solid',
borderColor: 'action.active'
}}
>
Recruit Talent
</HeroButton>
</Stack>
</Box>
<Box sx={{ justifyContent: "center", display: { xs: 'none', md: 'block' } }}>
<Box
component="img"
src={professionalConversationPng}
alt="Professional conversation"
sx={{
width: '100%',
maxWidth: 200,
height: 'auto',
borderRadius: 2,
boxShadow: 3,
}}
/>
</Box>
</Box>
</Container>
</HeroSection>
{/* How It Works Section */}
<Container sx={{ py: 8 }}>
<Typography
variant="h3"
component="h2"
align="center"
gutterBottom
sx={{ mb: 6, fontWeight: 600 }}
>
How Backstory Works
</Typography>
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', md: 'row' }, gap: 4 }}>
<Box sx={{ flex: 1 }}>
<Typography variant="h4" component="h3" gutterBottom sx={{ color: 'primary.main' }}>
For Job Seekers
</Typography>
<Box sx={{ my: 3 }}>
<Typography variant="body1" paragraph>
Backstory helps you tell your complete professional story, highlight your achievements, and showcase your skills beyond what fits on a traditional resume.
</Typography>
</Box>
<Stack spacing={3}>
<Box display="flex" alignItems="center">
<Box sx={{
backgroundColor: 'primary.main',
color: 'primary.contrastText',
borderRadius: '50%',
width: 40,
height: 40,
minWidth: 40,
minHeight: 40,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
mr: 2,
fontWeight: 'bold'
}}>
1
</Box>
<Typography variant="body1">
Upload your complete work history, projects, and achievements
</Typography>
</Box>
<Box display="flex" alignItems="center">
<Box sx={{
backgroundColor: 'primary.main',
color: 'primary.contrastText',
borderRadius: '50%',
width: 40,
height: 40,
minWidth: 40,
minHeight: 40,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
mr: 2,
fontWeight: 'bold'
}}>
2
</Box>
<Typography variant="body1">
Configure your AI assistant to answer questions about your experience
</Typography>
</Box>
<Box display="flex" alignItems="center">
<Box sx={{
backgroundColor: 'primary.main',
color: 'primary.contrastText',
borderRadius: '50%',
width: 40,
height: 40,
minWidth: 40,
minHeight: 40,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
mr: 2,
fontWeight: 'bold'
}}>
3
</Box>
<Typography variant="body1">
Generate targeted resumes based on specific job descriptions
</Typography>
</Box>
</Stack>
<Button
variant="contained"
color="secondary"
sx={{ mt: 4 }}
endIcon={<ArrowForwardIcon />}
>
Create Your Profile
</Button>
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="h4" component="h3" gutterBottom sx={{ color: 'primary.main' }}>
For Employers
</Typography>
<Box sx={{ my: 3 }}>
<Typography variant="body1" paragraph>
Discover candidates with the perfect skills and experience for your positions by engaging in meaningful Q&A to learn more about their background.
</Typography>
</Box>
<Stack spacing={3}>
<Box display="flex" alignItems="center">
<Box sx={{
backgroundColor: 'secondary.main',
color: 'secondary.contrastText',
borderRadius: '50%',
width: 40,
height: 40,
minWidth: 40,
minHeight: 40,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
mr: 2,
fontWeight: 'bold'
}}>
1
</Box>
<Typography variant="body1">
Search the candidate pool based on skills, experience, and location
</Typography>
</Box>
<Box display="flex" alignItems="center">
<Box sx={{
backgroundColor: 'secondary.main',
color: 'secondary.contrastText',
borderRadius: '50%',
width: 40,
height: 40,
minWidth: 40,
minHeight: 40,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
mr: 2,
fontWeight: 'bold'
}}>
2
</Box>
<Typography variant="body1">
Ask personalized questions about candidates' experience and skills
</Typography>
</Box>
<Box display="flex" alignItems="center">
<Box sx={{
backgroundColor: 'secondary.main',
color: 'secondary.contrastText',
borderRadius: '50%',
width: 40,
height: 40,
minWidth: 40,
minHeight: 40,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
mr: 2,
fontWeight: 'bold'
}}>
3
</Box>
<Typography variant="body1">
Generate targeted resumes that match your job requirements
</Typography>
</Box>
</Stack>
<Button
variant="contained"
color="secondary"
sx={{ mt: 4 }}
endIcon={<ArrowForwardIcon />}
>
Start Recruiting
</Button>
</Box>
</Box>
</Container>
{/* Features Section */}
<Box sx={{ backgroundColor: 'background.paper', py: 8 }}>
<Container>
<Typography
variant="h3"
component="h2"
align="center"
gutterBottom
sx={{ mb: 6, fontWeight: 600 }}
>
Key Features
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
<Box sx={{ flex: '1 1 250px', minWidth: { xs: '100%', sm: 'calc(50% - 16px)', md: 'calc(25% - 16px)' } }}>
<FeatureCard
icon={
<FeatureIcon>
<PersonSearchIcon fontSize="large" />
</FeatureIcon>
}
title="AI-Powered Search"
description="Find the perfect candidates based on skills, experience, and fit for your specific requirements."
/>
</Box>
<Box sx={{ flex: '1 1 250px', minWidth: { xs: '100%', sm: 'calc(50% - 16px)', md: 'calc(25% - 16px)' } }}>
<FeatureCard
icon={
<FeatureIcon>
<WorkHistoryIcon fontSize="large" />
</FeatureIcon>
}
title="Complete Backstory"
description="Share your full professional journey beyond the limitations of a traditional resume."
/>
</Box>
<Box sx={{ flex: '1 1 250px', minWidth: { xs: '100%', sm: 'calc(50% - 16px)', md: 'calc(25% - 16px)' } }}>
<FeatureCard
icon={
<FeatureIcon>
<QuestionAnswerIcon fontSize="large" />
</FeatureIcon>
}
title="Interactive Q&A"
description="Ask detailed questions about a candidate's experience and get immediate answers."
/>
</Box>
<Box sx={{ flex: '1 1 250px', minWidth: { xs: '100%', sm: 'calc(50% - 16px)', md: 'calc(25% - 16px)' } }}>
<FeatureCard
icon={
<FeatureIcon>
<DescriptionIcon fontSize="large" />
</FeatureIcon>
}
title="Custom Resumes"
description="Generate tailored resumes optimized for specific job descriptions with one click."
/>
</Box>
</Box>
</Container>
</Box>
{/* Testimonials Section */}
<Container sx={{ py: 8 }}>
<Typography
variant="h3"
component="h2"
align="center"
gutterBottom
sx={{ mb: 2, fontWeight: 600 }}
>
Success Stories
</Typography>
<Typography
variant="body1"
align="center"
sx={{ mb: 6, maxWidth: 800, mx: 'auto' }}
>
See how Backstory has transformed the hiring process for both candidates and employers.
</Typography>
<Testimonials />
</Container>
{/* CTA Section */}
<Box sx={{
backgroundColor: 'primary.main',
color: 'primary.contrastText',
py: 8
}}>
<Container>
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
maxWidth: 800,
mx: 'auto'
}}>
<Typography variant="h3" component="h2" gutterBottom>
Ready to transform your hiring process?
</Typography>
<Typography variant="h6" sx={{ mb: 4 }}>
Join Backstory today and discover a better way to connect talent with opportunity.
</Typography>
<Stack
direction={{ xs: 'column', sm: 'row' }}
spacing={2}
justifyContent="center"
>
<HeroButton
variant="contained"
size="large"
>
Sign Up as Candidate
</HeroButton>
<HeroButton
variant="outlined"
size="large"
sx={{
backgroundColor: 'transparent',
border: '2px solid',
borderColor: 'action.active'
}}
>
Sign Up as Employer
</HeroButton>
</Stack>
</Box>
</Container>
</Box>
</Box>
);
};
export {
HomePage
};

View File

@ -0,0 +1,518 @@
import React, { useState } from 'react';
import {
AppBar, Avatar, Box, Button, Chip, Container, Divider, Drawer,
IconButton, InputBase, List, ListItem, ListItemButton, ListItemIcon,
ListItemText, Paper, Tab, Tabs, TextField, Typography,
useMediaQuery, useTheme
} from '@mui/material';
import {
Menu as MenuIcon, Search as SearchIcon, Description as FileTextIcon,
Person as UserIcon, Settings as SettingsIcon, Add as PlusIcon,
Edit as EditIcon, Visibility as EyeIcon, Save as SaveIcon,
Delete as TrashIcon, AccessTime as ClockIcon, ChevronRight as ChevronRightIcon
} from '@mui/icons-material';
interface Resume {
id: number;
name: string;
date: string;
isRecent: boolean;
}
const MockupPage = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const [activeTab, setActiveTab] = useState<string>("resume");
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState<boolean>(false);
const [selectedResume, setSelectedResume] = useState<number | null>(null);
// Mock data
const savedResumes: Resume[] = [
{ id: 1, name: "Software Engineer - Tech Co", date: "May 15, 2025", isRecent: true },
{ id: 2, name: "Product Manager - StartupX", date: "May 10, 2025", isRecent: false },
{ id: 3, name: "Data Scientist - AI Corp", date: "May 5, 2025", isRecent: false },
];
return (
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100vh', bgcolor: 'background.default' }}>
{/* Header */}
<AppBar position="static" color="default" elevation={1} sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', px: 2, py: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="h6" component="h1" fontWeight="bold" color="text.primary">Backstory</Typography>
{isMobile && (
<IconButton edge="start" color="inherit" onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}>
<MenuIcon />
</IconButton>
)}
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
{!isMobile && (
<Button
startIcon={<PlusIcon />}
color="primary"
size="small"
>
New Resume
</Button>
)}
<IconButton sx={{ bgcolor: 'action.hover', borderRadius: '50%' }}>
<UserIcon />
</IconButton>
</Box>
</Box>
</AppBar>
<Box sx={{ display: 'flex', flex: 1, overflow: 'hidden' }}>
{/* Sidebar - hidden on mobile */}
{!isMobile && (
<Drawer
variant="permanent"
sx={{
width: 240,
flexShrink: 0,
[`& .MuiDrawer-paper`]: { width: 240, boxSizing: 'border-box', position: 'relative' },
}}
>
<Box sx={{ p: 2, display: 'flex', flexDirection: 'column', height: '100%' }}>
<Box sx={{ mb: 3 }}>
<Typography variant="overline" color="text.secondary" gutterBottom>Main</Typography>
<List disablePadding>
<ListItem disablePadding>
<ListItemButton sx={{ borderRadius: 1 }}>
<ListItemIcon sx={{ minWidth: 36 }}>
<SearchIcon fontSize="small" />
</ListItemIcon>
<ListItemText primary="Q&A" />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton
sx={{ borderRadius: 1, bgcolor: 'primary.lighter', color: 'primary.main' }}
>
<ListItemIcon sx={{ minWidth: 36, color: 'primary.main' }}>
<FileTextIcon fontSize="small" />
</ListItemIcon>
<ListItemText primary="Resume Builder" />
</ListItemButton>
</ListItem>
</List>
</Box>
<Box sx={{ mb: 3 }}>
<Typography variant="overline" color="text.secondary" gutterBottom>My Content</Typography>
<List disablePadding>
<ListItem disablePadding>
<ListItemButton sx={{ borderRadius: 1 }}>
<ListItemIcon sx={{ minWidth: 36 }}>
<FileTextIcon fontSize="small" />
</ListItemIcon>
<ListItemText primary="My Resumes" />
<Chip
label={savedResumes.length}
size="small"
sx={{ height: 20, fontSize: 12 }}
/>
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton sx={{ borderRadius: 1 }}>
<ListItemIcon sx={{ minWidth: 36 }}>
<SettingsIcon fontSize="small" />
</ListItemIcon>
<ListItemText primary="Settings" />
</ListItemButton>
</ListItem>
</List>
</Box>
<Box sx={{ mt: 'auto' }}>
<Paper variant="outlined" sx={{ p: 2, bgcolor: 'background.default' }}>
<Typography variant="subtitle2" color="text.primary" gutterBottom>Recent Activity</Typography>
<List dense disablePadding>
{savedResumes.filter(r => r.isRecent).map(resume => (
<ListItem key={resume.id} disablePadding sx={{ mb: 0.5 }}>
<ListItemIcon sx={{ minWidth: 24 }}>
<ClockIcon fontSize="small" />
</ListItemIcon>
<Typography variant="body2" noWrap>{resume.name}</Typography>
</ListItem>
))}
</List>
</Paper>
</Box>
</Box>
</Drawer>
)}
{/* Mobile menu - drawer */}
<Drawer
anchor="left"
open={isMobileMenuOpen}
onClose={() => setIsMobileMenuOpen(false)}
sx={{
display: { xs: 'block', md: 'none' },
'& .MuiDrawer-paper': { width: 240 }
}}
>
<Box sx={{ p: 2, display: 'flex', flexDirection: 'column', height: '100%' }}>
<Box sx={{ mb: 3 }}>
<Typography variant="overline" color="text.secondary" gutterBottom>Main</Typography>
<List disablePadding>
<ListItem disablePadding>
<ListItemButton onClick={() => setIsMobileMenuOpen(false)}>
<ListItemIcon>
<SearchIcon fontSize="small" />
</ListItemIcon>
<ListItemText primary="Q&A" />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton
onClick={() => setIsMobileMenuOpen(false)}
sx={{ bgcolor: 'primary.lighter', color: 'primary.main' }}
>
<ListItemIcon sx={{ color: 'primary.main' }}>
<FileTextIcon fontSize="small" />
</ListItemIcon>
<ListItemText primary="Resume Builder" />
</ListItemButton>
</ListItem>
</List>
</Box>
<Box sx={{ mb: 3 }}>
<Typography variant="overline" color="text.secondary" gutterBottom>My Content</Typography>
<List disablePadding>
<ListItem disablePadding>
<ListItemButton onClick={() => setIsMobileMenuOpen(false)}>
<ListItemIcon>
<FileTextIcon fontSize="small" />
</ListItemIcon>
<ListItemText primary="My Resumes" />
<Chip
label={savedResumes.length}
size="small"
sx={{ height: 20, fontSize: 12 }}
/>
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton onClick={() => setIsMobileMenuOpen(false)}>
<ListItemIcon>
<SettingsIcon fontSize="small" />
</ListItemIcon>
<ListItemText primary="Settings" />
</ListItemButton>
</ListItem>
</List>
</Box>
</Box>
</Drawer>
{/* Main content */}
<Box sx={{ flex: 1, overflow: 'auto', p: 3 }}>
{/* Resume Builder content */}
<Box sx={{ mb: 4 }}>
<Typography variant="h5" component="h2" fontWeight="bold" gutterBottom>Resume Builder</Typography>
<Typography variant="body2" color="text.secondary">Generate and customize resumes based on job descriptions</Typography>
</Box>
{/* Tabs */}
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs
value={activeTab}
onChange={(_, newValue) => setActiveTab(newValue)}
aria-label="Resume builder tabs"
variant={isMobile ? "scrollable" : "standard"}
scrollButtons={isMobile ? "auto" : undefined}
>
<Tab label="Job Description" value="job" />
<Tab label="Resume" value="resume" />
<Tab label="Fact Check" value="fact" />
<Tab label="Saved Resumes" value="saved" />
</Tabs>
</Box>
{/* Tab content */}
{activeTab === 'job' && (
<Paper variant="outlined" sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>Job Description</Typography>
<TextField
fullWidth
multiline
rows={10}
placeholder="Paste the job description here..."
variant="outlined"
/>
<Box sx={{ mt: 3 }}>
<Button variant="contained" color="primary">
Generate Resume
</Button>
</Box>
</Paper>
)}
{activeTab === 'resume' && (
<Paper variant="outlined" sx={{ p: 3 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography variant="h6">Resume Editor</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
variant="outlined"
size="small"
startIcon={<SaveIcon />}
>
Save
</Button>
<Button
variant="outlined"
size="small"
startIcon={<EyeIcon />}
>
Preview
</Button>
</Box>
</Box>
{/* Resume content editor with sections */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{/* Contact information */}
<Paper variant="outlined" sx={{ p: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="subtitle1" fontWeight="medium">Contact Information</Typography>
<IconButton size="small" color="default">
<EditIcon fontSize="small" />
</IconButton>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 2 }}>
<Box>
<Typography variant="caption" color="text.secondary" display="block" gutterBottom>
Full Name
</Typography>
<TextField size="small" fullWidth defaultValue="John Doe" />
</Box>
<Box>
<Typography variant="caption" color="text.secondary" display="block" gutterBottom>
Email
</Typography>
<TextField size="small" fullWidth defaultValue="john@example.com" />
</Box>
</Box>
</Paper>
{/* Professional Summary */}
<Paper variant="outlined" sx={{ p: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="subtitle1" fontWeight="medium">Professional Summary</Typography>
<IconButton size="small" color="default">
<EditIcon fontSize="small" />
</IconButton>
</Box>
<TextField
fullWidth
multiline
rows={3}
size="small"
defaultValue="Experienced software developer with 8+ years in full-stack development, specializing in React, Node.js, and cloud infrastructure. Passionate about creating scalable, maintainable applications."
/>
</Paper>
{/* Work Experience */}
<Paper variant="outlined" sx={{ p: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="subtitle1" fontWeight="medium">Work Experience</Typography>
<Button
startIcon={<PlusIcon />}
color="primary"
size="small"
>
Add Position
</Button>
</Box>
{/* Job entry */}
<Paper variant="outlined" sx={{ p: 2, mb: 2, bgcolor: 'background.default' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="subtitle2" fontWeight="medium">Senior Developer</Typography>
<Box sx={{ display: 'flex', gap: 0.5 }}>
<IconButton size="small">
<EditIcon fontSize="small" />
</IconButton>
<IconButton size="small" color="error">
<TrashIcon fontSize="small" />
</IconButton>
</Box>
</Box>
<Typography variant="body2" color="text.secondary">Tech Company Inc. 2020-Present</Typography>
<Box component="ul" sx={{ pl: 2, mt: 1 }}>
<Typography component="li" variant="body2">Led development of company's flagship product</Typography>
<Typography component="li" variant="body2">Improved performance by 40% through code optimization</Typography>
</Box>
</Paper>
</Paper>
{/* Add more sections button */}
<Button
variant="outlined"
fullWidth
sx={{
borderStyle: 'dashed',
p: 1.5,
color: 'text.secondary',
'&:hover': { bgcolor: 'background.default' }
}}
startIcon={<PlusIcon />}
>
Add Section
</Button>
</Box>
</Paper>
)} {activeTab === 'saved' && (
<Paper variant="outlined" sx={{ p: 3 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography variant="h6">Saved Resumes</Typography>
<Button
variant="contained"
color="primary"
size="small"
startIcon={<PlusIcon />}
>
New Resume
</Button>
</Box>
{/* Resume list */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{savedResumes.map(resume => (
<Paper
key={resume.id}
variant="outlined"
sx={{
p: 2,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
cursor: 'pointer',
bgcolor: selectedResume === resume.id ? 'primary.lighter' : 'background.paper',
borderColor: selectedResume === resume.id ? 'primary.light' : 'divider',
'&:hover': { bgcolor: selectedResume === resume.id ? 'primary.lighter' : 'action.hover' }
}}
onClick={() => setSelectedResume(resume.id)}
>
<Box>
<Typography variant="subtitle2">{resume.name}</Typography>
<Typography variant="caption" color="text.secondary">Last edited: {resume.date}</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<IconButton size="small">
<EditIcon fontSize="small" />
</IconButton>
<IconButton size="small" color="error">
<TrashIcon fontSize="small" />
</IconButton>
</Box>
</Paper>
))}
</Box>
</Paper>
)}
{activeTab === 'fact' && (
<Paper variant="outlined" sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>Fact Check</Typography>
<Typography variant="body2" color="text.secondary" paragraph>
This tab shows how your resume content compares to your employment history data.
</Typography>
<Box sx={{ mt: 2 }}>
<Paper variant="outlined" sx={{ p: 2, mb: 2, bgcolor: 'success.lighter' }}>
<Typography variant="subtitle2" fontWeight="medium" color="success.dark">
Work History Verification
</Typography>
<Typography variant="body2">
All employment dates match your documented history.
</Typography>
</Paper>
<Paper variant="outlined" sx={{ p: 2, mb: 2, bgcolor: 'warning.lighter' }}>
<Typography variant="subtitle2" fontWeight="medium" color="warning.dark">
Skills Verification
</Typography>
<Typography variant="body2">
Some skills listed (React Native, Flutter) are not strongly supported by your experience documents.
</Typography>
</Paper>
</Box>
</Paper>
)}
</Box>
</Box>
{/* Mobile bottom navigation */}
{isMobile && (
<Paper
sx={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
display: 'flex',
justifyContent: 'space-around',
borderTop: 1,
borderColor: 'divider',
zIndex: 1100
}}
elevation={3}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
py: 1,
px: 2,
color: 'text.secondary'
}}
component="button"
>
<SearchIcon />
<Typography variant="caption">Q&A</Typography>
</Box>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
py: 1,
px: 2,
color: 'primary.main'
}}
component="button"
>
<FileTextIcon />
<Typography variant="caption">Resume</Typography>
</Box>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
py: 1,
px: 2,
color: 'text.secondary'
}}
component="button"
>
<UserIcon />
<Typography variant="caption">Profile</Typography>
</Box>
</Paper>
)}
</Box>
);
}
export {
MockupPage
};

View File

@ -0,0 +1,34 @@
import React from 'react';
const RegisterPage = () => {
return (
<pre>
+------------------------------------------------------+
| BACKSTORY [Logo] Home |
+------------------------------------------------------+
| |
| Create Your Candidate Account |
| |
| [ ] Email |
| [ ] Password |
| [ ] Confirm Password |
| |
| [ ] I agree to the Terms & Privacy Policy |
| |
| [Create Account] |
| |
| Already have an account? [Login] |
| |
| --- or --- |
| |
| [Continue with Google] |
| [Continue with LinkedIn] |
| |
+------------------------------------------------------+
</pre>
);
};
export {
RegisterPage
};

View File

@ -76,6 +76,15 @@ const AboutPage = (props: BackstoryPageProps) => {
submitQuery: submitQuery,
setSnack,
}} />
<Document {...{
title: "BETA",
filepath: "/docs/beta.md",
onExpand: (open: boolean) => { onDocumentExpand('beta', open); },
expanded: page === 'beta',
sessionId,
submitQuery: submitQuery,
setSnack,
}} />
<Document {...{
title: "Resume Generation Architecture",
filepath: "/docs/resume-generation.md",

View File

@ -93,10 +93,6 @@ What would you like to know about ${user.first_name}?
}
return <Conversation
sx={{
maxWidth: "1024px",
height: "calc(100vh - 72px)",
}}
ref={ref}
{...{
multiline: true,

View File

@ -1,14 +0,0 @@
#!/bin/bash
fail() {
echo "FAIL: ${*}" >&2
exit 1
}
for i in {8..16}; do
if [[ -d "${i}" ]]; then
echo "Skipping lkml/git/$i -- already exists"
else
cmd="git clone https://erol.kernel.org/lkml/git/$i"
${cmd} || fail "cmd"
fi
done

View File

@ -991,14 +991,14 @@ class WebServer:
matching_user = next((user for user in self.users if user.username == username), None)
if matching_user:
user = matching_user
logger.info("Found matching user", user.model_dump(mode="json"))
logger.info(f"Found matching user: {user.username}")
else:
user = User(username=username, llm=self.llm)
user.initialize(prometheus_collector=self.prometheus_collector)
logger.info("Created new instance of user", user.model_dump(mode="json"))
logger.info(f"Created new instance of user: {user.username}")
self.users.append(user)
logger.info(f"Creating context {context_id} with user", user.model_dump(mode='json'))
logger.info(f"Creating context {context_id or "new"} for user: {user.username}")
try:
if context_id:
context = Context(

View File

@ -239,8 +239,6 @@ class JobDescription(Agent):
# Parse the result if it's a string
if isinstance(result, str):
try:
import json
result = json.loads(result)
assessment = result.get("skill_assessment", {})
except:
@ -468,8 +466,6 @@ Provide the resume in clean markdown format, ready for the candidate to use.
# Parse if string
if isinstance(result, str):
try:
import json
result = json.loads(result)
except:
continue

View File

@ -1,4 +1,8 @@
{
"first_name": "Eliza",
"last_name": "Morgan"
"first_name": "Eliza",
"last_name": "Morgan",
"questions": [
"Is Eliza real?",
"What are Eliza's skills?"
]
}

View File

@ -1,9 +1,12 @@
Eliza Morgan
Portland, Oregon | eliza.morgan@email.com | (555) 123-4567
linkedin.com/in/elizamorgan | ORCID: 0000-0002-XXXX-XXXX
Portland, Oregon | eliza.morgan@nomail.com | (555) 867-5309
linkedin.com/in/elizamorgan18383
**Professional Summary**
I am a ficticious persona generated by AI to seed the Backstory system for
testing, evaluation, and demo purposes.
Conservation botanist with over a decade of experience leading ecological restoration projects, advancing rare plant propagation methods, and managing native plant programs across the Pacific Northwest. Proven record of scientific innovation, collaborative project leadership, and effective stakeholder engagement. Passionate about preserving botanical diversity through applied research, restoration, and public education.
**Professional Experience**