✨ feat(card): add Business Card page
adds a page with print functionality
This commit is contained in:
@@ -10,6 +10,7 @@ const ProjectsPage = React.lazy(() => import('components/pages/ProjectsPage/inde
|
||||
const CurriculumPage = React.lazy(() => import('components/pages/CurriculumPage/index.js'))
|
||||
const ContactPage = React.lazy(() => import('components/pages/ContactPage/index.js'))
|
||||
const PrivacyPage = React.lazy(() => import('components/pages/PrivacyPage/index.js'))
|
||||
const BusinessCardPage = React.lazy(() => import('components/pages/BusinessCardPage/index.js'))
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@@ -57,6 +58,14 @@ const router = createBrowserRouter([
|
||||
</React.Suspense>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'card',
|
||||
element: (
|
||||
<React.Suspense fallback={<Loading />}>
|
||||
<BusinessCardPage />
|
||||
</React.Suspense>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/* Print styles for business card */
|
||||
@media print {
|
||||
body * {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#main,
|
||||
#page-content {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.print-business-card,
|
||||
.print-business-card * {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.print-business-card {
|
||||
width: 85mm;
|
||||
height: 55mm;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: 85mm 55mm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import React from 'react'
|
||||
import { Box, Typography, Stack, Button, Paper, Link, TypographyProps, SvgIcon, PaperProps, ThemeProvider, createTheme } from '@mui/material'
|
||||
import { Email, LinkedIn, Print, PhoneInTalk, Public } from '@mui/icons-material'
|
||||
|
||||
import { Page } from 'components/templates/index.js'
|
||||
import typography from 'style/typography/index.js'
|
||||
import palette from 'style/palette/index.js'
|
||||
import './index.css'
|
||||
|
||||
const NAME = import.meta.env.VITE_BC_NAME || '[FULL NAME]'
|
||||
const POSITION = import.meta.env.VITE_BC_POSITION || '[POSITION]'
|
||||
const VAT = import.meta.env.VITE_BC_VAT || '[VAT NUMBER]'
|
||||
const PHONE = import.meta.env.VITE_BC_PHONE || '[Redacted for privacy]'
|
||||
const EMAIL = import.meta.env.VITE_BC_EMAIL || '[Redacted for privacy]'
|
||||
const LINKEDIN = import.meta.env.VITE_BC_LINKEDIN || '[Redacted for privacy]'
|
||||
|
||||
const TypographyLink = ({ icon: Icon, children, ...props }: TypographyProps<typeof Link, { icon: typeof SvgIcon }>) => (
|
||||
<Box
|
||||
component={Link}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
underline="hover"
|
||||
color="text.secondary"
|
||||
{...props}
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: 0.5, ...props.sx }}>
|
||||
<Icon
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
fontSize: '0.7rem',
|
||||
}}
|
||||
/>
|
||||
<Typography variant="caption" color="text.secondary" fontSize="0.55rem">
|
||||
{children}
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
|
||||
const theme = {
|
||||
light: createTheme({
|
||||
typography: typography,
|
||||
palette: palette.light,
|
||||
}),
|
||||
dark: createTheme({
|
||||
typography: typography,
|
||||
palette: palette.dark,
|
||||
}),
|
||||
}
|
||||
|
||||
type BusinessCardProps = {
|
||||
mode?: 'light' | 'dark'
|
||||
src: string
|
||||
alt?: string
|
||||
} & PaperProps
|
||||
const BusinessCard = ({ mode = 'dark', src, alt, sx, ...props }: BusinessCardProps) => (
|
||||
<ThemeProvider theme={theme[mode]}>
|
||||
<Paper
|
||||
className="print-business-card"
|
||||
elevation={1}
|
||||
sx={{
|
||||
colorScheme: `${mode} !important`,
|
||||
backgroundColor: 'background.default',
|
||||
width: '85mm',
|
||||
height: '55mm',
|
||||
padding: '6mm',
|
||||
borderRadius: 2,
|
||||
borderWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
borderColor: mode === 'light' ? 'background.default' : 'white',
|
||||
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1.5,
|
||||
|
||||
'@media print': {
|
||||
backgroundColor: mode === 'light' ? 'white' : 'background.default',
|
||||
'-webkit-print-color-adjust': 'exact !important',
|
||||
'print-color-adjust': 'exact !important',
|
||||
elevation: 0,
|
||||
borderRadius: 0,
|
||||
borderColor: 'transparent',
|
||||
gap: 1.5,
|
||||
},
|
||||
...sx,
|
||||
}}
|
||||
{...props}>
|
||||
<Box
|
||||
component="img"
|
||||
src={src}
|
||||
alt={alt}
|
||||
sx={{
|
||||
width: '30%',
|
||||
height: 'fit-content',
|
||||
objectFit: 'cover',
|
||||
// borderRadius: '50%',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Right side with content */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
{props.children}
|
||||
</Box>
|
||||
</Paper>
|
||||
</ThemeProvider>
|
||||
)
|
||||
|
||||
/**
|
||||
* BusinessCardPage component - A digital business card
|
||||
* Designed to be printable as a standard business card (3.5 x 2 inches)
|
||||
*/
|
||||
const BusinessCardPage = () => {
|
||||
return (
|
||||
<Page>
|
||||
<Stack
|
||||
sx={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
pt: 4,
|
||||
gap: 2,
|
||||
'@media print': {
|
||||
pt: 0,
|
||||
gap: 0,
|
||||
},
|
||||
}}>
|
||||
<BusinessCard src="/fav-icon.svg" alt="logo">
|
||||
<Typography variant="h5" component="h1" fontFamily="Oswald, sans-serif" textTransform="uppercase" fontWeight={700}>
|
||||
{NAME}
|
||||
</Typography>
|
||||
<Typography variant="subtitle2" color="text.secondary" sx={{ fontSize: '0.55rem', mb: 0.5 }}>
|
||||
{POSITION}
|
||||
</Typography>
|
||||
</BusinessCard>
|
||||
|
||||
<BusinessCard src="/images/qr.svg" alt="Qr Code" mode="light">
|
||||
<Stack
|
||||
sx={{
|
||||
mt: 0.5,
|
||||
borderLeft: '1px solid',
|
||||
borderColor: 'divider',
|
||||
pl: 2,
|
||||
}}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
color="text.secondary"
|
||||
fontSize="0.55rem"
|
||||
fontFamily="Oswald, sans-serif"
|
||||
textTransform="uppercase"
|
||||
fontWeight={700}>
|
||||
{NAME}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary" fontSize="0.55rem" mb={0.5}>
|
||||
{VAT}
|
||||
</Typography>
|
||||
<TypographyLink icon={PhoneInTalk} href={`tel:${PHONE.replace(/[^\d+]/g, '').replace(/^\+?/, '+')}`}>
|
||||
{PHONE}
|
||||
</TypographyLink>
|
||||
<TypographyLink icon={Email} href={`mailto:${EMAIL}`}>
|
||||
{EMAIL}
|
||||
</TypographyLink>
|
||||
<TypographyLink icon={Public} href="https://www.m-tartari.eu">
|
||||
www.m-tartari.eu
|
||||
</TypographyLink>
|
||||
<TypographyLink icon={LinkedIn} href={`https://www.linkedin.com/in/${LINKEDIN}/`}>
|
||||
{LINKEDIN}
|
||||
</TypographyLink>
|
||||
</Stack>
|
||||
</BusinessCard>
|
||||
</Stack>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
ariaLabel: 'Print Business Card',
|
||||
my: 7,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
'@media print': {
|
||||
display: 'none',
|
||||
},
|
||||
}}>
|
||||
<Button variant="contained" startIcon={<Print />} onClick={() => window.print()}>
|
||||
Print Business Card
|
||||
</Button>
|
||||
</Box>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export default BusinessCardPage
|
||||
@@ -2,5 +2,6 @@ export { default as MainPage } from './MainPage'
|
||||
export { default as ContactPage } from './ContactPage'
|
||||
export { default as CurriculumPage } from './CurriculumPage'
|
||||
export { default as ProjectsPage } from './ProjectsPage'
|
||||
export { default as BusinessCardPage } from './BusinessCardPage'
|
||||
|
||||
export { default as ErrorPage } from './ErrorPage'
|
||||
|
||||
Vendored
+6
@@ -2,6 +2,12 @@
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_GA_MEASUREMENT_ID: string
|
||||
readonly VITE_BC_NAME: string
|
||||
readonly VITE_BC_POSITION: string
|
||||
readonly VITE_BC_VAT: string
|
||||
readonly VITE_BC_PHONE: string
|
||||
readonly VITE_BC_EMAIL: string
|
||||
readonly VITE_BC_LINKEDIN: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
||||
Reference in New Issue
Block a user