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