Compare commits

...

14 Commits

  1. 1
      .gitattributes
  2. 29
      README.md
  3. 14
      back/volume/package-lock.json
  4. 2
      back/volume/package.json
  5. 2
      back/volume/src/auth/42.strategy.ts
  6. 2
      back/volume/src/auth/auth.controller.ts
  7. 13
      back/volume/src/chat/chat.gateway.ts
  8. 5
      back/volume/src/main.ts
  9. 2
      back/volume/src/users/dto/user.dto.ts
  10. 75
      back/volume/src/users/users.controller.ts
  11. 33
      back/volume/src/users/users.service.ts
  12. 3
      back/volume/tsconfig.json
  13. BIN
      front/volume/public/img/profileicon.png
  14. 191
      front/volume/src/App.svelte
  15. 36
      front/volume/src/Auth.ts
  16. 92
      front/volume/src/components/Channels.svelte
  17. 42
      front/volume/src/components/Chat2.svelte
  18. 48
      front/volume/src/components/Friends.svelte
  19. 17
      front/volume/src/components/NavBar.svelte
  20. 56
      front/volume/src/components/Profile.svelte

1
.gitattributes

@ -1,2 +1,3 @@
*.ts test=auto eol=lf
*.svelte test=auto eol=lf
*.sh test=auto eol=lf

29
README.md

@ -15,19 +15,22 @@ If you not use rootless docker, either rename Makesudo as Makefile or call `make
rename .env_sample to .env and customize it to your needs and credentials.
## Back endpoints:
|Method|endpoint|description|
|:---:|:---:|:---:|
|GET |/log/in |the login using 42 api.|
|GET |/log/inReturn |the 42 api callback.|
|GET |/log/profile |get user 42's datas.|
|GET |/log/out |log out user.|
|GET |/ |return user datas.|
|POST|/ |update user datas.|
|GET |/friends |return users which are friends.|
|GET |/invits |return users which invited user to be friend.|
|POST|/invit/:id |invit user whith ftId: id as friend.|
|GET |/avatar |return the user avatar|
|POST|/avatar |set a user avatar with multipart post upload.|
|Method|endpoint|description|account securised?|
|:---:|:---:|:---:|:---:|
|GET |/log/in |the login using 42 api.|☑|
|GET |/log/inReturn |the 42 api callback.|☑|
|GET |/log/profile |get connected user 42's datas.|☑|
|GET |/log/out |log out user.|☑|
|GET |/all |return all users publics datas.|☒|
|GET |/online |return all online users's public datas.|☒|
|GET |/:id |return ftId: id's public datas|☒|
|GET |/ |return connected user public datas|☑|
|POST|/ |update user datas.|☑|
|GET |/friends |return users which are friends.|☑|
|GET |/invits |return users which invited user to be friend.|☑|
|POST|/invit/:id |invit user whith ftId: id as friend.|☑|
|GET |/avatar |return the user avatar|☒|
|POST|/avatar |set a user avatar with multipart post upload.|☑|
## Dependencies:

14
back/volume/package-lock.json

@ -28,7 +28,7 @@
"@types/express": "^4.17.13",
"@types/express-session": "^1.17.6",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/node": "^16.18.14",
"@types/passport": "^1.0.12",
"@types/ws": "^8.5.3",
"bcrypt": "^5.1.0",
@ -2336,9 +2336,9 @@
}
},
"node_modules/@types/node": {
"version": "16.18.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.3.tgz",
"integrity": "sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg=="
"version": "16.18.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.14.tgz",
"integrity": "sha512-wvzClDGQXOCVNU4APPopC2KtMYukaF1MN/W3xAmslx22Z4/IF1/izDMekuyoUlwfnDHYCIZGaj7jMwnJKBTxKw=="
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@ -12716,9 +12716,9 @@
}
},
"@types/node": {
"version": "16.18.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.3.tgz",
"integrity": "sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg=="
"version": "16.18.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.14.tgz",
"integrity": "sha512-wvzClDGQXOCVNU4APPopC2KtMYukaF1MN/W3xAmslx22Z4/IF1/izDMekuyoUlwfnDHYCIZGaj7jMwnJKBTxKw=="
},
"@types/parse-json": {
"version": "4.0.0",

2
back/volume/package.json

@ -40,7 +40,7 @@
"@types/express": "^4.17.13",
"@types/express-session": "^1.17.6",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/node": "^16.18.14",
"@types/passport": "^1.0.12",
"@types/ws": "^8.5.3",
"bcrypt": "^5.1.0",

2
back/volume/src/auth/42.strategy.ts

@ -36,7 +36,7 @@ export class FtStrategy extends PassportStrategy(Strategy, '42') {
if ((await this.usersService.findUser(ftId)) === null) {
const newUser = new User()
newUser.ftId = profile.id as number
newUser.username = profile.displayName as string
newUser.username = profile.username as string
newUser.avatar = ftId + '.jpg'
this.usersService.create(newUser)
const file = createWriteStream('avatars/' + ftId + '.jpg')

2
back/volume/src/auth/auth.controller.ts

@ -29,7 +29,7 @@ export class AuthController {
}
@Get('out')
@Redirect('/')
@Redirect('http://' + process.env.HOST + ':' + process.env.FRONT_PORT + '/')
logOut (@Req() req: Request) {
req.logOut(function (err) {
if (err) return err

13
back/volume/src/chat/chat.gateway.ts

@ -37,8 +37,10 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
async handleConnection (socket: Socket) {
try {
const user: User = await this.userService.findUser(socket.data.user.ftId)
if (!user) {
const user: User | null = await this.userService.findUser(
socket.data.user.ftId
)
if (user == null) {
socket.emit('Error', new UnauthorizedException())
// socket.disconnect();
return
@ -62,10 +64,11 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
async onCreateChannel (
socket: Socket,
@MessageBody() channeldto: CreateChannelDto
): Promise<Channel> {
): Promise<Channel | null> {
const channel = new Channel()
channel.name = channeldto.name
const owner = await this.userService.findUser(channeldto.owner)
if (owner == null) return null
channel.owners.push(owner)
channel.password = channeldto.password
/// ...///
@ -96,7 +99,9 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
const channel = await this.chatService.getChannel(
createdMessage.channel.id
)
const users = await this.userService.findOnlineInChannel(channel)
if (channel != null) {
const users = await this.userService.findOnlineInChannel(channel)
}
/// TODO: Send message to users
}
}

5
back/volume/src/main.ts

@ -7,11 +7,12 @@ import * as session from 'express-session'
import * as passport from 'passport'
import { type NestExpressApplication } from '@nestjs/platform-express'
import * as cookieParser from 'cookie-parser'
import { Response } from 'express'
async function bootstrap () {
const logger = new Logger()
const app = await NestFactory.create<NestExpressApplication>(AppModule)
const port = process.env.BACK_PORT
const port = process.env.BACK_PORT!
const cors = {
origin: ['http://localhost:80', 'http://localhost', '*'],
methods: 'GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS',
@ -24,7 +25,7 @@ async function bootstrap () {
session({
resave: false,
saveUninitialized: false,
secret: process.env.JWT_SECRET
secret: process.env.JWT_SECRET!
})
)
app.use(cookieParser())

2
back/volume/src/users/dto/user.dto.ts

@ -5,7 +5,7 @@ import { Express } from 'express'
export class UserDto {
@IsPositive()
@IsNotEmpty()
@IsOptional()
readonly ftId: number
@IsString()

75
back/volume/src/users/users.controller.ts

@ -33,20 +33,14 @@ import { join } from 'path'
export class UsersController {
constructor (private readonly usersService: UsersService) {}
@Get()
@Get('all')
async getAllUsers (): Promise<User[]> {
return await this.usersService.findUsers()
}
@Post()
@UseGuards(AuthenticatedGuard)
async create (@Body() payload: UserDto, @FtUser() profile: Profile) {
const user = await this.usersService.findUser(profile.id)
if (user) {
return await this.usersService.update(user.id, payload)
} else {
return await this.usersService.create(payload)
}
@Get('online')
async getOnlineUsers (): Promise<User[]> {
return await this.usersService.findOnlineUsers()
}
@Get('friends')
@ -61,15 +55,6 @@ export class UsersController {
return await this.usersService.getInvits(profile.id)
}
@Post('invit/:id')
@UseGuards(AuthenticatedGuard)
async invitUser (
@FtUser() profile: Profile,
@Param('id', ParseIntPipe) id: number
) {
return await this.usersService.invit(profile.id, id)
}
@Post('avatar')
@UseGuards(AuthenticatedGuard)
@UseInterceptors(
@ -95,15 +80,39 @@ export class UsersController {
@FtUser() profile: Profile,
@UploadedFile() file: Express.Multer.File
) {
return await this.usersService.addAvatar(profile.id, file.filename)
await this.usersService.addAvatar(profile.id, file.filename)
}
@Get('avatar')
@UseGuards(AuthenticatedGuard)
async getAvatar (
@FtUser() profile: Profile,
@Res({ passthrough: true }) response: Response
) {
const user = await this.usersService.findUser(profile.id)
return await this.getAvatarById(profile.id, response)
}
@Get('user/:name')
async getUserByName (@Param('name') username: string): Promise<User | null> {
return await this.usersService.findUserByName(username)
}
@Get('invit/:id')
@UseGuards(AuthenticatedGuard)
async invitUser (
@FtUser() profile: Profile,
@Param('id', ParseIntPipe) id: number
) {
return await this.usersService.invit(profile.id, id)
}
@Get('avatar/:id')
async getAvatarById (
@Param('id', ParseIntPipe) ftId: number,
@Res({ passthrough: true }) response: Response
) {
const user = await this.usersService.findUser(ftId)
if (user == null) return
const filename = user.avatar
const stream = createReadStream(join(process.cwd(), 'avatars/' + filename))
response.set({
@ -112,4 +121,28 @@ export class UsersController {
})
return new StreamableFile(stream)
}
@Get(':id')
async getUserById (
@Param('id', ParseIntPipe) ftId: number
): Promise<User | null> {
return await this.usersService.findUser(ftId)
}
@Get()
@UseGuards(AuthenticatedGuard)
async getUser (@FtUser() profile: Profile): Promise<User | null> {
return await this.usersService.findUser(profile.id)
}
@Post()
@UseGuards(AuthenticatedGuard)
async create (@Body() payload: UserDto, @FtUser() profile: Profile) {
const user = await this.usersService.findUser(profile.id)
if (user != null) {
return await this.usersService.update(user, payload)
} else {
return await this.usersService.create(payload)
}
}
}

33
back/volume/src/users/users.service.ts

@ -20,7 +20,11 @@ export class UsersService {
}
async findUser (ftId: number): Promise<User | null> {
return await this.usersRepository.findOneBy({ftId})
return await this.usersRepository.findOneBy({ ftId })
}
async findOnlineUsers (): Promise<User[]> {
return await this.usersRepository.find({ where: { status: 'online' } })
}
async create (userData: UserDto) {
@ -40,25 +44,28 @@ export class UsersService {
.getMany()
}
async update (ftId: number, changes: UserDto) {
const updatedUser = await this.findUser(ftId)
this.usersRepository.merge(updatedUser, changes)
return await this.usersRepository.save(updatedUser)
async update (user: User, changes: UserDto): Promise<User | null> {
this.usersRepository.merge(user, changes)
return await this.usersRepository.save(user)
}
async addAvatar (ftId: number, filename: string) {
return await this.usersRepository.update(ftId, {
avatar: filename
})
return await this.usersRepository.update(
{ ftId },
{
avatar: filename
}
)
}
async getFriends (ftId: number) {
async getFriends (ftId: number): Promise<User[]> {
const user = await this.usersRepository.findOne({
where: { ftId },
relations: {
friends: true,
friends: true
}
})
if (user == null) return []
return user.friends
}
@ -66,16 +73,18 @@ export class UsersService {
const user = await this.usersRepository.findOne({
where: { ftId },
relations: {
followers: true,
followers: true
}
})
if (user == null) return null
return user.followers
}
async invit (ftId: number, targetFtId: number) {
const user = await this.findUser(ftId)
if (user == null) return null
const target = await this.findUser(targetFtId)
if (!target) {
if (target == null) {
return new NotFoundException(
`Error: user id ${targetFtId} isn't in our db.`
)

3
back/volume/tsconfig.json

@ -13,6 +13,7 @@
"incremental": true,
"skipLibCheck": true,
"alwaysStrict": true,
"noImplicitAny": true
"noImplicitAny": true,
"strictNullChecks": true
}
}

BIN
front/volume/public/img/profileicon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

191
front/volume/src/App.svelte

@ -1,4 +1,5 @@
<script lang="ts">
import { onMount } from "svelte";
import Navbar from "./components/NavBar.svelte";
import Profile from "./components/Profile.svelte";
import MatchHistory from "./components/MatchHistory.svelte";
@ -11,9 +12,17 @@
import Pong from "./components/Pong/Pong.svelte";
import Chat2 from "./components/Chat2.svelte";
import type { chatMessagesType } from "./components/Chat2.svelte";
import Channels from "./components/Channels.svelte";
import type { ChannelsType } from "./components/Channels.svelte";
import { store, getUser, login, logout, API_URL } from "./Auth";
// PROFILE
onMount(() => {
getUser();
});
let isProfileOpen = false;
function clickProfile() {
isProfileOpen = true;
@ -36,18 +45,26 @@
// FRIENDS
let friends: Friend[] = [];
let invits: Friend[] = [];
export async function getFriends(): Promise<Friend[]> {
let response = await fetch(API_URL + "/friends", {
credentials: "include",
});
return await response.json();
}
export async function getInvits(): Promise<Friend[]> {
let response = await fetch(API_URL + "/invits", { credentials: "include" });
return await response.json();
}
let isFriendOpen = false;
function clickFriends() {
async function clickFriends() {
isFriendOpen = true;
friends = await getFriends();
invits = await getInvits();
}
let friends: Array<Friend> = [
{ username: "Alice", status: "online" },
{ username: "Bob", status: "online" },
{ username: "Charlie", status: "offline" },
{ username: "Dave", status: "offline" },
{ username: "Eve", status: "in a game" },
{ username: "Frank", status: "online" },
];
// SPECTATE
let isSpectateOpen = false;
@ -63,78 +80,102 @@
{ player1: "Alice", player2: "Bob", id: "3" },
];
// CHAT
let isChatOpen = false;
function clickChat() {
isChatOpen = true;
// CHANNELS
let isChannelsOpen = false;
function clickChannels() {
isChannelsOpen = true;
}
let chatMessages: Array<chatMessagesType> = [
{ name: "Alice", text: "Bob" },
{ name: "Alice", text: "Bob" },
{ name: "Alice", text: "Bob" },
{ name: "Alice", text: "Bob" },
{ name: "Alice", text: "Bob" },
{ name: "Alice", text: "Bob" },
let channels: Array<ChannelsType> = [
{ id: "1", name: "General", messages: [], privacy: "public", password: "" },
{
id: "2",
name: "Lobby",
messages: [],
privacy: "private",
password: "test",
},
{ id: "3", name: "Game", messages: [], privacy: "private", password: "" },
];
let selectedChannel: ChannelsType;
const handleSelectChannel = (channel: ChannelsType) => {
selectedChannel = channel;
};
</script>
<main>
<Navbar
{clickProfile}
{clickHistory}
{clickFriends}
{clickSpectate}
{clickChat}
/>
{#if isChatOpen}
<div
on:click={() => (isChatOpen = false)}
on:keydown={() => (isChatOpen = false)}
>
<Chat2 {chatMessages} />
</div>
{/if}
{#if isSpectateOpen}
<div
on:click={() => (isSpectateOpen = false)}
on:keydown={() => (isSpectateOpen = false)}
>
<Spectate {spectate} />
</div>
{/if}
{#if isFriendOpen}
<div
on:click={() => (isFriendOpen = false)}
on:keydown={() => (isFriendOpen = false)}
>
<Friends {friends} />
</div>
{/if}
{#if isHistoryOpen}
<div
on:click={() => (isHistoryOpen = false)}
on:keydown={() => (isHistoryOpen = false)}
>
<MatchHistory {matches} />
</div>
{/if}
{#if isProfileOpen}
<div
on:click={() => (isProfileOpen = false)}
on:keydown={() => (isProfileOpen = false)}
>
<Profile
username="Alice"
wins={10}
losses={5}
elo={256}
rank={23}
is2faEnabled={false}
<div>
{#if $store === null}
<h1><button type="button" on:click={login}>Log In</button></h1>
{:else}
<h1><button type="button" on:click={logout}>Log Out</button></h1>
<Navbar
{clickProfile}
{clickHistory}
{clickFriends}
{clickSpectate}
{clickChannels}
/>
</div>
{/if}
<Play />
<Pong />
{#if isChannelsOpen}
{#if selectedChannel}
<div
on:click={() => (selectedChannel = undefined)}
on:keydown={() => (selectedChannel = undefined)}
>
<Chat2 chatMessages={selectedChannel.messages} />
</div>
{/if}
{#if !selectedChannel}
<div
on:click={() => (isChannelsOpen = false)}
on:keydown={() => (isChannelsOpen = false)}
>
<Channels {channels} onSelectChannel={handleSelectChannel} />
</div>
{/if}
{/if}
{#if isSpectateOpen}
<div
on:click={() => (isSpectateOpen = false)}
on:keydown={() => (isSpectateOpen = false)}
>
<Spectate {spectate} />
</div>
{/if}
{#if isFriendOpen}
<div
on:click={() => (isFriendOpen = false)}
on:keydown={() => (isFriendOpen = false)}
>
<Friends {friends} {invits} />
</div>
{/if}
{#if isHistoryOpen}
<div
on:click={() => (isHistoryOpen = false)}
on:keydown={() => (isHistoryOpen = false)}
>
<MatchHistory {matches} />
</div>
{/if}
{#if isProfileOpen}
<div
on:click={() => (isProfileOpen = false)}
on:keydown={() => (isProfileOpen = false)}
>
<Profile
username={$store.username}
wins={10}
losses={5}
elo={256}
rank={23}
is2faEnabled={false}
/>
</div>
{/if}
<Play />
<Pong />
{/if}
</div>
</main>
<style>

36
front/volume/src/Auth.ts

@ -0,0 +1,36 @@
import { writable } from "svelte/store";
let _user = localStorage.getItem("user");
export const store = writable(_user ? JSON.parse(_user) : null);
store.subscribe((value) => {
if (value) localStorage.setItem("user", JSON.stringify(value));
else localStorage.removeItem("user");
});
export const API_URL =
"http://" + import.meta.env.VITE_HOST + ":" + import.meta.env.VITE_BACK_PORT;
export async function getUser() {
const res = await fetch(API_URL, {
method: "get",
mode: "cors",
cache: "no-cache",
credentials: "include",
redirect: "follow",
referrerPolicy: "no-referrer",
});
let user = await res.json();
if (user.username) {
console.log(user);
store.set(user);
}
}
export function login() {
window.location.replace(API_URL + "/log/in");
}
export function logout() {
window.location.replace(API_URL + "/log/out");
store.set(null);
}

92
front/volume/src/components/Channels.svelte

@ -0,0 +1,92 @@
<script lang="ts" context="module">
import type { chatMessagesType } from "./Chat2.svelte";
export interface ChannelsType {
id: string;
name: string;
privacy: string;
password: string;
messages: Array<chatMessagesType>;
}
</script>
<script lang="ts">
export let channels: Array<ChannelsType> = [];
export let onSelectChannel: (channel: ChannelsType) => void;
const selectChat = (id: string) => {
const channel = channels.find((c) => c.id === id);
if (channel) {
onSelectChannel(channel);
}
};
const createChannel = () => {
const name = prompt("Enter a name for the new channel:");
if (name) {
const privacy = prompt(
"Enter a privacy setting for the new channel (public/private):"
);
if (privacy !== "public" && privacy !== "private") {
alert("Invalid privacy setting");
}
let password = "";
if (privacy === "private") {
password = prompt("Enter a password for the new channel:");
if (!password) {
alert("Invalid password");
}
}
if (privacy === "public" || password) {
const newChannel: ChannelsType = {
id: Math.random().toString(),
name,
privacy,
password,
messages: [],
};
channels = [newChannel, ...channels];
}
}
// TODO: save to database
};
</script>
<div class="overlay">
<div class="channels" on:click|stopPropagation on:keydown|stopPropagation>
<div>
{#if channels.length > 0}
<h2>Channels</h2>
{#each channels.slice(0, 10) as _channels}
<li>
<span>{_channels.name}</span>
<button on:click={() => selectChat(_channels.id)}>Enter</button>
</li>
{/each}
{:else}
<p>No channels available</p>
{/if}
<button on:click={createChannel}>Create Channel</button>
</div>
</div>
</div>
<style>
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9998;
display: flex;
justify-content: center;
align-items: center;
}
.channels {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 5px;
padding: 1rem;
width: 300px;
}
</style>

42
front/volume/src/components/Chat2.svelte

@ -1,5 +1,6 @@
<script lang="ts" context="module">
export interface chatMessagesType {
id: number;
name: string;
text: string;
}
@ -9,15 +10,42 @@
const sendMessage = () => {
if (newText !== "") {
const newMessage = {
id: chatMessages.length + 1,
name: "You",
text: newText,
};
chatMessages = [...chatMessages, newMessage];
chatMessages = [...chatMessages.slice(-5 + 1), newMessage];
newText = "";
const messagesDiv = document.querySelector(".messages");
if (messagesDiv) {
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
}
// TODO: save to database
};
export let chatMessages: Array<chatMessagesType> = [];
let newText = "";
const openProfile = (id: number) => (event: Event) => {
const message = chatMessages.find((m) => m.id === id);
if (message) {
const optionsModal = document.createElement("div");
optionsModal.classList.add("options-modal");
optionsModal.innerHTML = `
<h3>${message.name}</h3>
<ul>
<li>View profile</li>
<li>View posts</li>
<li>View comments</li>
</ul>
`;
document.querySelector(".overlay")?.appendChild(optionsModal);
optionsModal.addEventListener("click", () => {
document.body.removeChild(optionsModal);
});
}
};
</script>
<div class="overlay">
@ -25,7 +53,12 @@
<div class="messages">
{#each chatMessages as message}
<p class="message">
<span class="message-name">
<span
class="message-name"
on:click={openProfile(message.id)}
on:keydown={openProfile(message.id)}
style="cursor: pointer;"
>
{message.name}
</span>: {message.text}
</p>
@ -61,4 +94,9 @@
padding: 1rem;
width: 300px;
}
.messages {
height: 200px;
overflow-y: scroll;
}
</style>

48
front/volume/src/components/Friends.svelte

@ -2,11 +2,16 @@
export interface Friend {
username: string;
status: "online" | "offline" | "in a game";
ftId: number;
}
</script>
<script lang="ts">
export let friends: Array<Friend> = [];
import { API_URL } from "../Auth";
export let friends: Friend[];
export let invits: Friend[];
async function addFriend(event: any) {
console.log(typeof event);
@ -15,20 +20,23 @@
console.log(usernameInput);
const username = usernameInput.value;
// const response = await fetch('', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify({ username })
// });
// if (response.ok) {
// console.log('Friend added successfully');
// } else {
// console.log('Failed to add friend');
// }
// usernameInput.value = '';
alert("Trying to add friend" + username);
let response = await fetch(API_URL + "/user/" + username, {
credentials: "include",
mode: "cors",
});
let target = await response.json();
response = await fetch(API_URL + "/invit/" + target.ftId, {
credentials: "include",
});
if (response.ok) {
console.log("Invitation send.");
} else {
console.log("Unknown user.");
}
usernameInput.value = "";
alert("Trying to add friend: " + username);
}
</script>
@ -45,6 +53,16 @@
{:else}
<p>No friends to display</p>
{/if}
{#if invits.length > 0}
<h2>Monkey invits</h2>
{#each invits.slice(0, 10) as invit}
<li>
<span>{invit.username} invited you to be friend.</span>
</li>
{/each}
{:else}
<p>No invitations to display</p>
{/if}
<div>
<h3>Add a friend</h3>
<form on:submit={addFriend}>

17
front/volume/src/components/NavBar.svelte

@ -1,8 +1,13 @@
<script lang="ts">
let api =
"http://" +
import.meta.env.VITE_HOST +
":" +
import.meta.env.VITE_BACK_PORT;
export let links = [
{ text: "Home", url: "img/pong.png" },
{ text: "Spectate" },
{ text: "Chat" },
{ text: "Channels" },
{ text: "History" },
{ text: "Friends" },
{ text: "Profile" },
@ -11,7 +16,7 @@
export let clickHistory = () => {};
export let clickFriends = () => {};
export let clickSpectate = () => {};
export let clickChat = () => {};
export let clickChannels = () => {};
</script>
<nav class="navigation-bar">
@ -24,10 +29,10 @@
</button>
</li>
{/if}
{#if link.text === "Chat"}
{#if link.text === "Channels"}
<li>
<button on:click={clickChat}>
<p>Chat</p>
<button on:click={clickChannels}>
<p>Channels</p>
</button>
</li>
{/if}
@ -41,7 +46,7 @@
{#if link.text === "Profile"}
<li>
<button on:click={clickProfile}>
<img src="img/profileicon.png" alt="profile icon" />
<img src={api + "/avatar"} alt="avatar" />
</button>
</li>
{/if}

56
front/volume/src/components/Profile.svelte

@ -1,34 +1,27 @@
<script lang="ts">
export let username = "";
import { API_URL, store } from "../Auth";
export let username = $store.userame;
export let realname = "";
export let wins = 0;
export let losses = 0;
export let elo = 0;
export let rank = -1;
export let is2faEnabled = false;
async function handleSubmit(event: Event) {
event.preventDefault();
// const response = await fetch('', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify({
// username
// })
// });
// if (response.ok) {
// console.log('username updated');
// }
// else {
// console.log('username update failed');
// }
alert("Trying to update username to " + username);
}
async function handleAvatarUpload(event: Event) {
event.preventDefault();
alert("Trying to upload avatar");
async function handleSubmit() {
let response = await fetch(API_URL, {
headers: { "content-type": "application/json" },
method: "POST",
body: JSON.stringify({ username: username }),
credentials: "include",
});
if (response.ok) {
alert("Succefully changed username.");
$store.username = username;
}
}
async function handle2fa(event: Event) {
event.preventDefault();
alert("Trying to " + (is2faEnabled ? "disable" : "enable") + " 2FA");
@ -38,19 +31,24 @@
<div class="overlay">
<div class="profile" on:click|stopPropagation on:keydown|stopPropagation>
<div class="profile-header">
<img class="profile-img" src="img/profileicon.png" alt="Profile Icon" />
<img class="profile-img" src={API_URL + "/avatar"} alt="avatar" />
<h3>{realname}</h3>
<form on:submit={handleAvatarUpload}>
<button type="submit">Upload Avatar</button>
<form
action={API_URL + "/avatar"}
method="post"
enctype="multipart/form-data"
>
<input type="file" id="avatar-input" name="avatar" />
<input type="submit" />
</form>
</div>
<div class="profile-body">
<form on:submit={handleSubmit}>
<form on:submit|preventDefault={handleSubmit}>
<div class="username">
<label for="username">Username</label>
<input type="text" id="username" bind:value={username} />
<button type="submit">Submit</button>
</div>
<button type="submit">Submit</button>
</form>
<p>Wins: {wins}</p>
<p>Losses: {losses}</p>
@ -89,7 +87,7 @@
border: 1px solid #ccc;
border-radius: 5px;
padding: 1rem;
width: 300px;
width: 375px;
}
.profile-header {

Loading…
Cancel
Save