feat: better session display
This commit is contained in:
parent
9bb6362a68
commit
b478e46055
|
@ -20,13 +20,15 @@
|
|||
"next": "14.2.6",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"showdown": "^2.1.0"
|
||||
"showdown": "^2.1.0",
|
||||
"ua-parser-js": "^1.0.38"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/node": "^20.16.2",
|
||||
"@types/react": "^18.3.4",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "14.2.6",
|
||||
"sass": "^1.77.8",
|
||||
|
|
|
@ -9,17 +9,20 @@ import { useEffect, useState } from 'react';
|
|||
import cookie from 'cookie';
|
||||
import Meta from '@/components/Meta';
|
||||
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
|
||||
import styles from '@/styles/Settings.module.scss';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
interface SettingsType {
|
||||
animations: boolean;
|
||||
ads: boolean;
|
||||
language: string;
|
||||
}
|
||||
|
||||
export default function Settings({ sessions }: { sessions: any[] }) {
|
||||
const [ settings, setSettings ] = useState<SettingsType>({
|
||||
animations: true,
|
||||
ads: true,
|
||||
language: 'en'
|
||||
ads: true
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -34,6 +37,9 @@ export default function Settings({ sessions }: { sessions: any[] }) {
|
|||
localStorage.setItem('crss_settings', JSON.stringify(settings));
|
||||
}, [ settings ]);
|
||||
|
||||
const currentSession = sessions.find(s => s.current);
|
||||
const otherSessions = sessions.filter(s => !s.current);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta page={{ title: 'Settings' }} />
|
||||
|
@ -42,54 +48,52 @@ export default function Settings({ sessions }: { sessions: any[] }) {
|
|||
<PageContent>
|
||||
<h1>Settings</h1>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '.5rem' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
{ /* TODO: Move it to the css 😒 */ }
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '.5rem', marginBottom: '1rem' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<label htmlFor="ani">Animations</label>
|
||||
<input id="ani" type="checkbox" checked={settings.animations} onChange={e => setSettings({ ...settings, animations: e.target.checked })} />
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<label htmlFor="ads">Ads</label>
|
||||
<input id="ads" type="checkbox" checked={settings.ads} onChange={e => setSettings({ ...settings, ads: e.target.checked })} />
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<label htmlFor="lang">Language</label>
|
||||
<select id="lang" onChange={e => setSettings({ ...settings, language: e.target.value })}>
|
||||
<option value="en">English</option>
|
||||
<option value="hu">Hungarian</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Sessions</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User Agent</th>
|
||||
<th>Created</th>
|
||||
<th>Expires</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sessions.map(session => (
|
||||
<tr key={session.id}>
|
||||
<td><code>{session.user_agent}</code></td>
|
||||
<td>{new Date(session.created).toLocaleDateString('hu-HU')}</td>
|
||||
<td>{new Date(session.expires).toLocaleDateString('hu-HU')}</td>
|
||||
<td>
|
||||
{session.current ? (
|
||||
<label>Current Session</label>
|
||||
) : (
|
||||
<button disabled>Sign Out</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
View and manage your active sessions.
|
||||
</p>
|
||||
|
||||
<h3>Current Session</h3>
|
||||
|
||||
<div className={styles.sessions}>
|
||||
<div className={styles.sessionCard}>
|
||||
<div>
|
||||
<p>{currentSession.user_agent.os.name} {currentSession.user_agent.os.version} • {currentSession.user_agent.browser.name} {currentSession.user_agent.browser.version}</p>
|
||||
<p>{new Date(currentSession.created).toLocaleString('en-GB')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Other Sessions</h3>
|
||||
|
||||
<div className={styles.sessions}>
|
||||
{otherSessions.map(session => (
|
||||
<div key={session.id} className={styles.sessionCard}>
|
||||
<div>
|
||||
<p>{session.user_agent.os.name} {session.user_agent.os.version} • {session.user_agent.browser.name} {session.user_agent.browser.version}</p>
|
||||
<p>{new Date(session.created).toLocaleString('en-GB')}</p>
|
||||
</div>
|
||||
|
||||
<button onClick={() => { alert('Not yet implemented, yeah...'); }}>
|
||||
<X />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PageContent>
|
||||
|
||||
<Footer />
|
||||
|
@ -123,8 +127,36 @@ export async function getServerSideProps(context: any) {
|
|||
|
||||
sessions.forEach((s: any) => {
|
||||
s.current = session.id === s.id;
|
||||
|
||||
s.created = s.created.toISOString();
|
||||
s.expires = s.expires.toISOString();
|
||||
|
||||
const ua = new UAParser(s.user_agent).getResult();
|
||||
|
||||
s.user_agent = {
|
||||
ua: ua.ua,
|
||||
browser: {
|
||||
name: ua.browser.name || null,
|
||||
version: ua.browser.version || null,
|
||||
major: ua.browser.major || null
|
||||
},
|
||||
engine: {
|
||||
name: ua.engine.name || null,
|
||||
version: ua.engine.version || null
|
||||
},
|
||||
os: {
|
||||
name: ua.os.name || null,
|
||||
version: ua.os.version || null
|
||||
},
|
||||
device: {
|
||||
vendor: ua.device.vendor || null,
|
||||
model: ua.device.model || null,
|
||||
type: ua.device.type || null
|
||||
},
|
||||
cpu: {
|
||||
architecture: ua.cpu.architecture || null
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -44,6 +44,9 @@ importers:
|
|||
showdown:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
ua-parser-js:
|
||||
specifier: ^1.0.38
|
||||
version: 1.0.38
|
||||
devDependencies:
|
||||
'@types/cookie':
|
||||
specifier: ^0.6.0
|
||||
|
@ -57,6 +60,9 @@ importers:
|
|||
'@types/react-dom':
|
||||
specifier: ^18.3.0
|
||||
version: 18.3.0
|
||||
'@types/ua-parser-js':
|
||||
specifier: ^0.7.39
|
||||
version: 0.7.39
|
||||
eslint:
|
||||
specifier: ^8.57.0
|
||||
version: 8.57.0
|
||||
|
@ -959,6 +965,9 @@ packages:
|
|||
'@types/react@18.3.4':
|
||||
resolution: {integrity: sha512-J7W30FTdfCxDDjmfRM+/JqLHBIyl7xUIp9kwK637FGmY7+mkSFSe6L4jpZzhj5QMfLssSDP4/i75AKkrdC7/Jw==}
|
||||
|
||||
'@types/ua-parser-js@0.7.39':
|
||||
resolution: {integrity: sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==}
|
||||
|
||||
'@typescript-eslint/parser@7.2.0':
|
||||
resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
|
@ -2523,6 +2532,9 @@ packages:
|
|||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
ua-parser-js@1.0.38:
|
||||
resolution: {integrity: sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==}
|
||||
|
||||
unbox-primitive@1.0.2:
|
||||
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
|
||||
|
||||
|
@ -3710,6 +3722,8 @@ snapshots:
|
|||
'@types/prop-types': 15.7.12
|
||||
csstype: 3.1.3
|
||||
|
||||
'@types/ua-parser-js@0.7.39': {}
|
||||
|
||||
'@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 7.2.0
|
||||
|
@ -4303,7 +4317,7 @@ snapshots:
|
|||
eslint: 8.57.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
||||
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0)
|
||||
eslint-plugin-react: 7.35.0(eslint@8.57.0)
|
||||
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
|
||||
|
@ -4334,7 +4348,7 @@ snapshots:
|
|||
is-bun-module: 1.1.0
|
||||
is-glob: 4.0.3
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
||||
transitivePeerDependencies:
|
||||
- '@typescript-eslint/parser'
|
||||
- eslint-import-resolver-node
|
||||
|
@ -4352,7 +4366,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
|
||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
|
||||
dependencies:
|
||||
array-includes: 3.1.8
|
||||
array.prototype.findlastindex: 1.2.5
|
||||
|
@ -5519,6 +5533,8 @@ snapshots:
|
|||
|
||||
typescript@5.5.4: {}
|
||||
|
||||
ua-parser-js@1.0.38: {}
|
||||
|
||||
unbox-primitive@1.0.2:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
|
55
styles/Settings.module.scss
Normal file
55
styles/Settings.module.scss
Normal file
|
@ -0,0 +1,55 @@
|
|||
@import 'variables.module';
|
||||
@import 'global.module';
|
||||
|
||||
.sessions {
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
gap: .5rem;
|
||||
|
||||
> .sessionCard {
|
||||
display: flex;
|
||||
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
background: transparent;
|
||||
color: $colorTextLight1;
|
||||
|
||||
border: none;
|
||||
border-radius: 100%;
|
||||
|
||||
padding: 8px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
transition: 150ms;
|
||||
|
||||
&:hover {
|
||||
background: rgba($colorPrimary, 0.35);
|
||||
color: $colorPrimary;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: $colorTextDark1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ code, pre, kbd {
|
|||
$headers: (
|
||||
h1: 1.95rem,
|
||||
h2: 1.5rem,
|
||||
h3: 160%,
|
||||
h3: 1.25rem,
|
||||
h4: 140%,
|
||||
h5: 120%,
|
||||
h6: 110%
|
||||
|
|
Loading…
Reference in a new issue