Skip to content

Commit 0bf20b7

Browse files
Merge pull request #91 from Dans-Plugins/feat/track-and-show-visits
Feat/track and show visits
2 parents 8fce9b6 + ee85a26 commit 0bf20b7

File tree

9 files changed

+163
-7
lines changed

9 files changed

+163
-7
lines changed

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,12 @@ yarn-error.log*
3333

3434
# typescript
3535
*.tsbuildinfo
36+
37+
# data
38+
/data
39+
40+
#iml
41+
*.iml
42+
43+
#xml
44+
*.xml

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ RUN npm install
1212
COPY components ./components
1313
COPY pages ./pages
1414
COPY public ./public
15+
COPY services ./services
1516
COPY styles ./styles
1617
COPY utils ./utils
1718
COPY next-env.d.ts ./

components/BottomBar.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,38 @@ const VersionNumber: React.FC<{ version: string }> = ({version}) => (
3333
);
3434

3535
interface BottomBarProps {
36-
version: string
36+
version: string;
37+
visits?: number;
38+
startDate?: string;
3739
}
3840

39-
const BottomBar: React.FC<BottomBarProps> = ({version}) => {
41+
const BottomBar: React.FC<BottomBarProps> = ({version, visits, startDate}) => {
4042
const colorMode = useContext(ColorModeContext);
4143
const theme = useTheme();
4244

45+
const formattedDate = startDate ? new Date(startDate).toLocaleDateString('en-US', {
46+
year: 'numeric',
47+
month: 'long',
48+
day: 'numeric'
49+
}) : null;
50+
4351
return (
4452
<AppBar position="static" sx={(theme) => bottomAppBarStyle(theme)}>
4553
<Toolbar sx={(theme) => toolbarStyle(theme)}>
4654
<Box sx={(theme) => flexContainerStyle(theme)}>
47-
<VersionNumber version={version}/>
55+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
56+
<VersionNumber version={version}/>
57+
{visits !== undefined && startDate && (
58+
<>
59+
<Typography variant="body2" color="inherit">
60+
61+
</Typography>
62+
<Typography variant="body2" color="inherit">
63+
{visits} visits since {formattedDate}
64+
</Typography>
65+
</>
66+
)}
67+
</Box>
4868

4969
<Box sx={(theme) => flexContainerStyle(theme, {gap: 1})}>
5070
<FooterButton

components/VisitCounter.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from 'react';
2+
import { Typography, Box } from '@mui/material';
3+
4+
interface VisitCounterProps {
5+
visits: number;
6+
startDate: string;
7+
}
8+
9+
const VisitCounter: React.FC<VisitCounterProps> = ({ visits, startDate }) => {
10+
const formattedDate = new Date(startDate).toLocaleDateString('en-US', {
11+
year: 'numeric',
12+
month: 'long',
13+
day: 'numeric'
14+
});
15+
16+
return (
17+
<Box
18+
sx={{
19+
position: 'fixed',
20+
bottom: '80px',
21+
right: '20px',
22+
padding: '8px',
23+
borderRadius: '4px',
24+
backgroundColor: 'background.paper',
25+
boxShadow: 1
26+
}}
27+
>
28+
<Typography variant="body2">
29+
Page Visits: {visits}
30+
</Typography>
31+
<Typography variant="body2" sx={{ fontSize: '0.8em', opacity: 0.8 }}>
32+
Since {formattedDate}
33+
</Typography>
34+
</Box>
35+
);
36+
};
37+
38+
export default VisitCounter;

compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ services:
66
- "3000:3000"
77
volumes:
88
- ./node_modules:/app/node_modules
9-
restart: always
10-
9+
- ./data:/app/data
10+
restart: always

pages/api/visits.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { NextApiRequest, NextApiResponse } from 'next';
2+
import { initializeVisitStorage, getVisitData, incrementVisitCount } from '../../utils/visitStorage';
3+
4+
initializeVisitStorage();
5+
6+
export default function handler(req: NextApiRequest, res: NextApiResponse) {
7+
if (req.method === 'POST') {
8+
const data = incrementVisitCount();
9+
res.status(200).json(data);
10+
} else if (req.method === 'GET') {
11+
const data = getVisitData();
12+
res.status(200).json(data);
13+
} else {
14+
res.status(405).json({ message: 'Method not allowed' });
15+
}
16+
}

pages/index.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Blurb from '../components/Blurb'
55
import PluginCard from '../components/PluginCard'
66
import React from 'react';
77
import BottomBar from '../components/BottomBar'
8+
import { getVisits, incrementVisits } from '../services/visitService';
89

910
interface PluginData {
1011
mostPopular: string[];
@@ -80,7 +81,24 @@ const AllPlugins: React.FC = () => (
8081
</Box>
8182
)
8283

83-
const Home: NextPage = () => {
84+
interface HomeProps {
85+
visits: number;
86+
startDate: string;
87+
}
88+
89+
export const getServerSideProps = async () => {
90+
await incrementVisits();
91+
const data = await getVisits();
92+
93+
return {
94+
props: {
95+
visits: data.visits,
96+
startDate: data.startDate
97+
}
98+
};
99+
};
100+
101+
const Home: NextPage<HomeProps> = ({ visits, startDate }) => {
84102
return (
85103
<Box sx={pageStyle}>
86104
<TopBar/>
@@ -91,7 +109,11 @@ const Home: NextPage = () => {
91109
<SectionDivider/>
92110
<AllPlugins/>
93111
</Container>
94-
<BottomBar version={version}/>
112+
<BottomBar
113+
version={version}
114+
visits={visits}
115+
startDate={startDate}
116+
/>
95117
</Box>
96118
);
97119
};

services/visitService.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
interface VisitData {
2+
visits: number;
3+
startDate: string;
4+
}
5+
6+
const getBaseUrl = () => process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
7+
8+
export const incrementVisits = async (): Promise<void> => {
9+
await fetch(`${getBaseUrl()}/api/visits`, { method: 'POST' });
10+
};
11+
12+
export const getVisits = async (): Promise<VisitData> => {
13+
const response = await fetch(`${getBaseUrl()}/api/visits`);
14+
return response.json();
15+
};

utils/visitStorage.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
4+
const DATA_DIR = path.join(process.cwd(), 'data');
5+
const VISITS_FILE = path.join(DATA_DIR, 'visits.json');
6+
7+
interface VisitData {
8+
visits: number;
9+
startDate: string;
10+
}
11+
12+
export const initializeVisitStorage = () => {
13+
if (!fs.existsSync(DATA_DIR)) {
14+
fs.mkdirSync(DATA_DIR, { recursive: true });
15+
}
16+
17+
if (!fs.existsSync(VISITS_FILE)) {
18+
const initialData: VisitData = {
19+
visits: 0,
20+
startDate: new Date().toISOString()
21+
};
22+
fs.writeFileSync(VISITS_FILE, JSON.stringify(initialData));
23+
}
24+
};
25+
26+
export const getVisitData = (): VisitData => {
27+
return JSON.parse(fs.readFileSync(VISITS_FILE, 'utf8'));
28+
};
29+
30+
export const incrementVisitCount = (): VisitData => {
31+
const data = getVisitData();
32+
data.visits += 1;
33+
fs.writeFileSync(VISITS_FILE, JSON.stringify(data));
34+
return data;
35+
};

0 commit comments

Comments
 (0)