mirror of
https://github.com/vcscsvcscs/GenerationsHeritage.git
synced 2025-08-12 22:09:07 +02:00
Add login logic
This commit is contained in:
37
frontend/package-lock.json
generated
37
frontend/package-lock.json
generated
@@ -8,7 +8,9 @@
|
||||
"name": "frontend",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@dagrejs/dagre": "github:dagrejs/dagre",
|
||||
"@xyflow/svelte": "^0.0.41",
|
||||
"oidc-client-ts": "^3.0.1",
|
||||
"svelte-eslint-parser": "^0.33.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -572,6 +574,22 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dagrejs/dagre": {
|
||||
"version": "1.1.3-pre",
|
||||
"resolved": "git+ssh://git@github.com/dagrejs/dagre.git#e6d4c7f6f95834fc794d82e6c803ead3aa3816d2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dagrejs/graphlib": "2.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@dagrejs/graphlib": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-2.2.2.tgz",
|
||||
"integrity": "sha512-CbyGpCDKsiTg/wuk79S7Muoj8mghDGAESWGxcSyhHX5jD35vYMBZochYVFzlHxynpE9unpu6O+4ZuhrLxASsOg==",
|
||||
"engines": {
|
||||
"node": ">17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||
@@ -3205,6 +3223,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -3480,6 +3506,17 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/oidc-client-ts": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.0.1.tgz",
|
||||
"integrity": "sha512-xX8unZNtmtw3sOz4FPSqDhkLFnxCDsdo2qhFEH2opgWnF/iXMFoYdBQzkwCxAZVgt3FT3DnuBY3k80EZHT0RYg==",
|
||||
"dependencies": {
|
||||
"jwt-decode": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
|
@@ -30,7 +30,9 @@
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@dagrejs/dagre": "github:dagrejs/dagre",
|
||||
"@xyflow/svelte": "^0.0.41",
|
||||
"oidc-client-ts": "^3.0.1",
|
||||
"svelte-eslint-parser": "^0.33.1"
|
||||
}
|
||||
}
|
||||
|
@@ -1,51 +1,79 @@
|
||||
<!doctype html>
|
||||
<html lang="en" style="width: 100vw; height: 100vh;">
|
||||
<html lang="en" style="width: 100vw; height: 100vh">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<body data-sveltekit-preload-data="hover" style="width: 100vw; height: 100vh;">
|
||||
<div style="display: contents; width: 100vw; height: 100vh;" class="bg-base-100">%sveltekit.body%</div>
|
||||
<div class="dropdown mb-72" style="position: absolute; left: auto; right: 3vw; top: 10px; bottom: auto">
|
||||
<div tabindex="0" role="button" class="btn m-1">
|
||||
Theme
|
||||
<svg width="12px" height="12px" class="h-2 w-2 fill-current opacity-60 inline-block"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048">
|
||||
<path d="M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z"></path>
|
||||
</svg>
|
||||
<body data-sveltekit-preload-data="hover" style="width: 100vw; height: 100vh">
|
||||
<div style="display: contents; width: 100vw; height: 100vh" class="bg-base-100">
|
||||
%sveltekit.body%
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content z-[1] p-2 shadow-2xl bg-base-300 rounded-box w-36">
|
||||
<li>
|
||||
<input type="radio" name="theme-dropdown"
|
||||
class="theme-controller btn btn-sm btn-block btn-ghost justify-start" aria-label="Light"
|
||||
value="light" />
|
||||
</li>
|
||||
<li>
|
||||
<input type="radio" name="theme-dropdown"
|
||||
class="theme-controller btn btn-sm btn-block btn-ghost justify-start" aria-label="Dark"
|
||||
value="dark" />
|
||||
</li>
|
||||
<li>
|
||||
<input type="radio" name="theme-dropdown"
|
||||
class="theme-controller btn btn-sm btn-block btn-ghost justify-start" aria-label="Cyberpunk"
|
||||
value="cyberpunk" />
|
||||
</li>
|
||||
<li>
|
||||
<input type="radio" name="theme-dropdown"
|
||||
class="theme-controller btn btn-sm btn-block btn-ghost justify-start" aria-label="Synthwave"
|
||||
value="synthwave" />
|
||||
</li>
|
||||
<li>
|
||||
<input type="radio" name="theme-dropdown"
|
||||
class="theme-controller btn btn-sm btn-block btn-ghost justify-start" aria-label="Retro"
|
||||
value="retro" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<div
|
||||
class="dropdown mb-72"
|
||||
style="position: absolute; left: auto; right: 3vw; top: 10px; bottom: auto"
|
||||
>
|
||||
<div tabindex="0" role="button" class="btn m-1">
|
||||
Theme
|
||||
<svg
|
||||
width="12px"
|
||||
height="12px"
|
||||
class="h-2 w-2 fill-current opacity-60 inline-block"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 2048 2048"
|
||||
>
|
||||
<path d="M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content z-[1] p-2 shadow-2xl bg-base-300 rounded-box w-36">
|
||||
<li>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme-dropdown"
|
||||
class="theme-controller btn btn-sm btn-block btn-ghost justify-start"
|
||||
aria-label="Light"
|
||||
value="light"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme-dropdown"
|
||||
class="theme-controller btn btn-sm btn-block btn-ghost justify-start"
|
||||
aria-label="Dark"
|
||||
value="dark"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme-dropdown"
|
||||
class="theme-controller btn btn-sm btn-block btn-ghost justify-start"
|
||||
aria-label="Cyberpunk"
|
||||
value="cyberpunk"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme-dropdown"
|
||||
class="theme-controller btn btn-sm btn-block btn-ghost justify-start"
|
||||
aria-label="Synthwave"
|
||||
value="synthwave"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme-dropdown"
|
||||
class="theme-controller btn btn-sm btn-block btn-ghost justify-start"
|
||||
aria-label="Retro"
|
||||
value="retro"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
70
frontend/src/lib/auth.ts
Normal file
70
frontend/src/lib/auth.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { UserManager, WebStorageStateStore, User } from 'oidc-client-ts';
|
||||
import { isAuthenticated, user } from './stores';
|
||||
import {
|
||||
PUBLIC_ZITADEL_CLIENT_ID,
|
||||
PUBLIC_ISSUER,
|
||||
PUBLIC_LOGIN_REDIRECT_URI,
|
||||
PUBLIC_LOGOUT_REDIRECT_URI
|
||||
} from '$env/static/public';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
|
||||
|
||||
let userManager: UserManager;
|
||||
if (browser) {
|
||||
const config = {
|
||||
authority: PUBLIC_ISSUER, // At Zitadel Project Console > [Your project] > [Your application] > URLs - Issuer
|
||||
client_id: PUBLIC_ZITADEL_CLIENT_ID, // At Zitadel Project Console > [Your project] > [Your application] > Configuration - Client ID
|
||||
redirect_uri: PUBLIC_LOGIN_REDIRECT_URI+ '/callback', // At Zitadel Project Console > [Your project] > [Your application] > URLs - Login Redirect URI
|
||||
response_type: 'code',
|
||||
scope: 'openid profile email',
|
||||
post_logout_redirect_uri: PUBLIC_LOGOUT_REDIRECT_URI,
|
||||
userStore: new WebStorageStateStore({ store: window.localStorage }),
|
||||
automaticSilentRenew: true,
|
||||
silent_redirect_uri: PUBLIC_LOGIN_REDIRECT_URI + '/silent-refresh'
|
||||
};
|
||||
|
||||
userManager = new UserManager(config);
|
||||
|
||||
userManager.events.addUserLoaded((loadedUser: User) => {
|
||||
console.log('userManager.events.addUserLoaded');
|
||||
user.set(loadedUser);
|
||||
isAuthenticated.set(true);
|
||||
});
|
||||
|
||||
userManager.events.addUserUnloaded(() => {
|
||||
console.log('userManager.events.addUserUnloaded');
|
||||
user.set(null);
|
||||
isAuthenticated.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
async function login(): Promise<void> {
|
||||
console.log('UserManager.login()');
|
||||
if (browser) {
|
||||
await userManager.signinRedirect();
|
||||
}
|
||||
}
|
||||
|
||||
async function logout(): Promise<void> {
|
||||
if (browser) {
|
||||
await userManager.signoutRedirect();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCallback(): Promise<void> {
|
||||
if (browser) {
|
||||
await userManager.signinRedirectCallback();
|
||||
goto('/');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSilentCallback(): Promise<void> {
|
||||
if (browser) {
|
||||
await userManager.signinSilentCallback();
|
||||
goto('/');
|
||||
}
|
||||
}
|
||||
|
||||
export { login, logout, handleCallback, handleSilentCallback };
|
@@ -2,12 +2,11 @@
|
||||
import { Handle, Position } from '@xyflow/svelte';
|
||||
|
||||
export let person = {
|
||||
ID : "",
|
||||
Lastname : "Nem",
|
||||
Firstname : "Ismert",
|
||||
Middlename : "",
|
||||
ProfilePicture : "https://daisyui.com/images/stock/photo-1534528741775-53994a69daeb.jpg",
|
||||
|
||||
ID: '',
|
||||
Lastname: 'Nem',
|
||||
Firstname: 'Ismert',
|
||||
Middlename: '',
|
||||
ProfilePicture: 'https://www.flaticon.com/free-icons/user'
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -15,10 +14,14 @@
|
||||
<div class="card-body items-center text-center w-30">
|
||||
<div class="avatar">
|
||||
<figure class="w-24 mask mask-squircle">
|
||||
<img src="{person.ProfilePicture}" alt="Picture of {person.Lastname} {person.Firstname}" />
|
||||
<img src={person.ProfilePicture} alt="Picture of {person.Lastname} {person.Firstname}" />
|
||||
</figure>
|
||||
</div>
|
||||
<h2 class="card-title text-primary-content">{person.Lastname} {person.Firstname} {person.Middlename}</h2>
|
||||
<h2 class="card-title text-primary-content">
|
||||
{person.Lastname}
|
||||
{person.Firstname}
|
||||
{person.Middlename}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<Handle
|
||||
|
40
frontend/src/lib/family_tree/dagreLayout.ts
Normal file
40
frontend/src/lib/family_tree/dagreLayout.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import dagre from '@dagrejs/dagre';
|
||||
import { Position, type Node, type Edge } from '@xyflow/svelte';
|
||||
|
||||
const dagreGraph = new dagre.graphlib.Graph();
|
||||
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
||||
|
||||
const nodeWidth = 172;
|
||||
const nodeHeight = 36;
|
||||
|
||||
function getLayoutedElements(nodes: Node[], edges: Edge[], direction = 'TB') {
|
||||
const isHorizontal = direction === 'LR';
|
||||
dagreGraph.setGraph({ rankdir: direction });
|
||||
|
||||
nodes.forEach((node) => {
|
||||
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
|
||||
});
|
||||
|
||||
edges.forEach((edge) => {
|
||||
dagreGraph.setEdge(edge.source, edge.target);
|
||||
});
|
||||
|
||||
dagre.layout(dagreGraph);
|
||||
|
||||
nodes.forEach((node) => {
|
||||
const nodeWithPosition = dagreGraph.node(node.id);
|
||||
node.targetPosition = isHorizontal ? Position.Left : Position.Top;
|
||||
node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
|
||||
|
||||
// We are shifting the dagre node position (anchor=center center) to the top left
|
||||
// so it matches the React Flow node anchor point (top left).
|
||||
node.position = {
|
||||
x: nodeWithPosition.x - nodeWidth / 2,
|
||||
y: nodeWithPosition.y - nodeHeight / 2
|
||||
};
|
||||
});
|
||||
|
||||
return { nodes, edges };
|
||||
}
|
||||
|
||||
export { getLayoutedElements };
|
27
frontend/src/lib/family_tree/getFamilyTree.ts
Normal file
27
frontend/src/lib/family_tree/getFamilyTree.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { PUBLIC_API_URL } from '$env/static/public';
|
||||
import { user } from '$lib/stores';
|
||||
|
||||
let auth_token: string;
|
||||
|
||||
user.subscribe((value) => {
|
||||
if (value) {
|
||||
auth_token = value.access_token;
|
||||
}
|
||||
});
|
||||
|
||||
async function fetch_family_tree() {
|
||||
const response = await fetch(
|
||||
{ PUBLIC_API_URL } + '/familyTree?id=8a8b9b05bdc24550a5cc73e0b55e8d7d',
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: auth_token
|
||||
}
|
||||
}
|
||||
);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
export { fetch_family_tree };
|
5
frontend/src/lib/stores.ts
Normal file
5
frontend/src/lib/stores.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { User } from 'oidc-client-ts';
|
||||
|
||||
export const isAuthenticated = writable<boolean>(false);
|
||||
export const user = writable<User | null>(null);
|
@@ -1,47 +1,78 @@
|
||||
<script>
|
||||
import PersonNode from './../lib/family_tree/PersonNode.svelte';
|
||||
<script lang="ts">
|
||||
import { writable } from 'svelte/store';
|
||||
import { onMount } from 'svelte';
|
||||
import { SvelteFlowProvider, SvelteFlow, Controls, MiniMap } from '@xyflow/svelte';
|
||||
import type { Node, Edge, NodeTypes } from '@xyflow/svelte';
|
||||
import { isAuthenticated } from '../lib/stores';
|
||||
import PersonNode from './../lib/family_tree/PersonNode.svelte';
|
||||
import { login } from '../lib/auth';
|
||||
import { fetch_family_tree } from '$lib/family_tree/getFamilyTree';
|
||||
import { getLayoutedElements } from '$lib/family_tree/dagreLayout';
|
||||
|
||||
const nodes = writable([
|
||||
{
|
||||
id: '1',
|
||||
data: { label: 'Hello' },
|
||||
position: { x: 0, y: 0 }
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'World' },
|
||||
position: { x: 0, y: 150 }
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'World' },
|
||||
position: { x: 250, y: 150 },
|
||||
type: 'custom'
|
||||
let edges_data: {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
data: { type: string; verified: boolean };
|
||||
}[] = [];
|
||||
const nodes = writable<Node[]>([]);
|
||||
const edges = writable<Edge[]>([]);
|
||||
onMount(() => {
|
||||
if (!$isAuthenticated) {
|
||||
console.log('user authenticated:', $isAuthenticated);
|
||||
login();
|
||||
} else {
|
||||
console.log('user authenticated:', $isAuthenticated);
|
||||
console.log('fetching nodes');
|
||||
fetch_family_tree().then((data) => {
|
||||
let Position = { x: 0, y: 0 };
|
||||
let nodes_data: Node[] = [];
|
||||
function AddToNodesData(data: any, i: number) {
|
||||
if (data[0].Values[i] != null) {
|
||||
if (Object.prototype.toString.call(data[0].Values[i]) === '[object Array]') {
|
||||
data[0].Values[i].forEach((person: { ElementId: string; Props: {} }) => {
|
||||
nodes_data.push({
|
||||
id: person.ElementId,
|
||||
type: 'custom',
|
||||
data: person.Props,
|
||||
position: Position
|
||||
});
|
||||
});
|
||||
} else {
|
||||
nodes_data.push({
|
||||
id: data[0].Values[i].ElementId,
|
||||
type: 'custom',
|
||||
data: data[0].Values[i].Props,
|
||||
position: Position
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
AddToNodesData(data, 0);
|
||||
AddToNodesData(data, 2);
|
||||
AddToNodesData(data, 4);
|
||||
AddToNodesData(data, 6);
|
||||
AddToNodesData(data, 8);
|
||||
|
||||
edges_data = [];
|
||||
|
||||
const layoutedElements = getLayoutedElements(nodes_data, edges_data, 'TB');
|
||||
|
||||
$nodes = layoutedElements.nodes;
|
||||
$edges = layoutedElements.edges;
|
||||
});
|
||||
}
|
||||
]);
|
||||
const edges = writable([
|
||||
{
|
||||
id: '1-2',
|
||||
source: '1',
|
||||
target: '2'
|
||||
},
|
||||
{
|
||||
id: '1-3',
|
||||
source: '1',
|
||||
target: '3'
|
||||
}
|
||||
]);
|
||||
const nodeTypes = {
|
||||
});
|
||||
|
||||
const nodeTypes: NodeTypes = {
|
||||
custom: PersonNode
|
||||
};
|
||||
</script>
|
||||
|
||||
<div style="height:100vh;">
|
||||
<SvelteFlowProvider>
|
||||
<SvelteFlow {nodes} {nodeTypes} {edges} class="bg-base-100" fitView=True onlyRenderVisibleElements=True>
|
||||
<Controls class="bg-base-300 text-primary-content"/>
|
||||
<SvelteFlow {nodes} {nodeTypes} {edges} class="bg-base-100" fitView onlyRenderVisibleElements>
|
||||
<Controls class="bg-base-300 text-primary-content" />
|
||||
<MiniMap />
|
||||
</SvelteFlow>
|
||||
</SvelteFlowProvider>
|
||||
|
11
frontend/src/routes/callback/+page.svelte
Normal file
11
frontend/src/routes/callback/+page.svelte
Normal file
@@ -0,0 +1,11 @@
|
||||
<!-- src/routes/callback.svelte -->
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { handleCallback } from '../../lib/auth';
|
||||
|
||||
onMount(() => {
|
||||
handleCallback();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>Logging in...</div>
|
10
frontend/src/routes/siltent-refresh/+page.svelte
Normal file
10
frontend/src/routes/siltent-refresh/+page.svelte
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { handleSilentCallback } from '../../lib/auth';
|
||||
|
||||
onMount(() => {
|
||||
handleSilentCallback();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>Refreshing...</div>
|
14
frontend/tsconfig.json
Normal file
14
frontend/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["src/**/*", "src/node_modules", ".svelte-kit/ambient.d.ts"] // see last element
|
||||
}
|
Reference in New Issue
Block a user