diff --git a/.gitignore b/.gitignore index 1ec2f88..195310c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ pnpm-lock.yaml package-lock.json .idea .dev +openapi-template +nginx-dev +src/redocly.yaml +flake/ diff --git a/Docker.mk b/Docker.mk index 6e5bec6..3bc0a48 100644 --- a/Docker.mk +++ b/Docker.mk @@ -34,7 +34,8 @@ ifeq "$(REDUCED_SET)" "y" tic-tac-toe \ nginx \ user \ - pong + pong \ + icons endif all: build diff --git a/docker-compose.yml b/docker-compose.yml index 6f5362b..a1a7f26 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,7 +50,7 @@ services: - '9090:8443' volumes: # if you need to share files with nginx, you do it here. - - static-volume:/volumes/static + - icons-volume:/volumes/icons env_file: .env logging: driver: gelf @@ -72,7 +72,7 @@ services: - app volumes: - sqlite-volume:/volumes/database - - static-volume:/volumes/static + - icons-volume:/volumes/icons - ./src/auth/config:/config env_file: .env logging: @@ -98,7 +98,32 @@ services: - app volumes: - sqlite-volume:/volumes/database - - static-volume:/volumes/static + env_file: .env + logging: + driver: gelf + options: + gelf-address: "udp://127.0.0.1:12201" + tag: "{{.Name}}" + + + ############### + # ICONS # + ############### + icons: + build: + context: ./src/ + args: + - SERVICE=icons + additional_contexts: + pnpm_base: "service:pnpm_base" + pnpm_deps: "service:pnpm_deps" + container_name: app-icons + restart: always + networks: + - app + volumes: + - sqlite-volume:/volumes/database + - icons-volume:/volumes/icons env_file: .env logging: driver: gelf @@ -124,7 +149,6 @@ services: env_file: .env volumes: - sqlite-volume:/volumes/database - - static-volume:/volumes/static logging: driver: gelf options: @@ -148,7 +172,6 @@ services: - app volumes: - sqlite-volume:/volumes/database - - static-volume:/volumes/static env_file: .env logging: driver: gelf @@ -173,7 +196,6 @@ services: - app volumes: - sqlite-volume:/volumes/database - - static-volume:/volumes/static env_file: .env logging: driver: gelf @@ -358,6 +380,6 @@ services: volumes: sqlite-volume: - static-volume: + icons-volume: grafana-data: elastic-data: diff --git a/frontend/index.html b/frontend/index.html index 59f7892..2182ae4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -37,6 +37,7 @@ ⭕ Tic-Tac-Toe ▮•▮ Ping Pong 🏆 Tournaments + 😎 Friends 🚪 Logout diff --git a/frontend/src/api/generated/.openapi-generator/FILES b/frontend/src/api/generated/.openapi-generator/FILES index 0a4207f..dd4bc5c 100644 --- a/frontend/src/api/generated/.openapi-generator/FILES +++ b/frontend/src/api/generated/.openapi-generator/FILES @@ -1,6 +1,8 @@ apis/OpenapiOtherApi.ts apis/index.ts index.ts +models/AddFriend200Response.ts +models/AddFriend404Response.ts models/AllowGuestMessage200Response.ts models/AllowGuestMessage403Response.ts models/ChangeDesc200Response.ts @@ -38,6 +40,9 @@ models/GuestLogin200ResponsePayload.ts models/GuestLogin400Response.ts models/GuestLogin500Response.ts models/GuestLoginRequest.ts +models/ListFriend200Response.ts +models/ListFriend200ResponsePayload.ts +models/ListFriend200ResponsePayloadFriendsInner.ts models/Login200Response.ts models/Login202Response.ts models/Login202ResponsePayload.ts @@ -60,6 +65,8 @@ models/ProviderList200Response.ts models/ProviderList200ResponsePayload.ts models/ProviderList200ResponsePayloadListInner.ts models/ProviderList200ResponsePayloadListInnerColors.ts +models/RemoveFriend200Response.ts +models/RemoveFriend404Response.ts models/Signin200Response.ts models/Signin200ResponsePayload.ts models/Signin400Response.ts @@ -82,7 +89,6 @@ models/TournamentList404Response.ts models/TttHistory200Response.ts models/TttHistory200ResponsePayload.ts models/TttHistory200ResponsePayloadDataInner.ts -models/TttHistory200ResponsePayloadDataInnerPlayerX.ts models/TttHistory404Response.ts models/index.ts runtime.ts diff --git a/frontend/src/api/generated/apis/OpenapiOtherApi.ts b/frontend/src/api/generated/apis/OpenapiOtherApi.ts index 84da0d5..0ca170c 100644 --- a/frontend/src/api/generated/apis/OpenapiOtherApi.ts +++ b/frontend/src/api/generated/apis/OpenapiOtherApi.ts @@ -15,6 +15,8 @@ import * as runtime from '../runtime'; import type { + AddFriend200Response, + AddFriend404Response, AllowGuestMessage200Response, AllowGuestMessage403Response, ChangeDesc200Response, @@ -46,6 +48,7 @@ import type { GuestLogin400Response, GuestLogin500Response, GuestLoginRequest, + ListFriend200Response, Login200Response, Login202Response, Login400Response, @@ -60,6 +63,8 @@ import type { PongHistory200Response, PongHistory404Response, ProviderList200Response, + RemoveFriend200Response, + RemoveFriend404Response, Signin200Response, Signin400Response, Signin500Response, @@ -74,6 +79,10 @@ import type { TttHistory404Response, } from '../models/index'; import { + AddFriend200ResponseFromJSON, + AddFriend200ResponseToJSON, + AddFriend404ResponseFromJSON, + AddFriend404ResponseToJSON, AllowGuestMessage200ResponseFromJSON, AllowGuestMessage200ResponseToJSON, AllowGuestMessage403ResponseFromJSON, @@ -136,6 +145,8 @@ import { GuestLogin500ResponseToJSON, GuestLoginRequestFromJSON, GuestLoginRequestToJSON, + ListFriend200ResponseFromJSON, + ListFriend200ResponseToJSON, Login200ResponseFromJSON, Login200ResponseToJSON, Login202ResponseFromJSON, @@ -164,6 +175,10 @@ import { PongHistory404ResponseToJSON, ProviderList200ResponseFromJSON, ProviderList200ResponseToJSON, + RemoveFriend200ResponseFromJSON, + RemoveFriend200ResponseToJSON, + RemoveFriend404ResponseFromJSON, + RemoveFriend404ResponseToJSON, Signin200ResponseFromJSON, Signin200ResponseToJSON, Signin400ResponseFromJSON, @@ -190,6 +205,10 @@ import { TttHistory404ResponseToJSON, } from '../models/index'; +export interface AddFriendRequest { + user: string; +} + export interface ChangeDescOperationRequest { changeDescRequest: ChangeDescRequest; } @@ -226,6 +245,10 @@ export interface PongHistoryRequest { user: string; } +export interface RemoveFriendRequest { + user: string; +} + export interface SigninRequest { loginRequest: LoginRequest; } @@ -243,6 +266,60 @@ export interface TttHistoryRequest { */ export class OpenapiOtherApi extends runtime.BaseAPI { + /** + */ + async addFriendRaw(requestParameters: AddFriendRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters['user'] == null) { + throw new runtime.RequiredError( + 'user', + 'Required parameter "user" was null or undefined when calling addFriend().' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + + let urlPath = `/api/user/friend/add/{user}`; + urlPath = urlPath.replace(`{${"user"}}`, encodeURIComponent(String(requestParameters['user']))); + + const response = await this.request({ + path: urlPath, + method: 'PUT', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + // CHANGED: Handle all status codes defined in the OpenAPI spec, not just 2xx responses + // This allows typed access to error responses (4xx, 5xx) and other status codes. + // The code routes responses based on the actual HTTP status code and returns + // appropriately typed ApiResponse wrappers for each status code. + if (response.status === 200) { + // Object response for status 200 + return new runtime.JSONApiResponse(response, (jsonValue) => AddFriend200ResponseFromJSON(jsonValue)); + } + if (response.status === 401) { + // Object response for status 401 + return new runtime.JSONApiResponse(response, (jsonValue) => ChangePassword401ResponseFromJSON(jsonValue)); + } + if (response.status === 404) { + // Object response for status 404 + return new runtime.JSONApiResponse(response, (jsonValue) => AddFriend404ResponseFromJSON(jsonValue)); + } + // CHANGED: Throw error if status code is not handled by any of the defined responses + // This ensures all code paths return a value and provides clear error messages for unexpected status codes + // Only throw if responses were defined but none matched the actual status code + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 401, 404`); + } + + /** + */ + async addFriend(requestParameters: AddFriendRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.addFriendRaw(requestParameters, initOverrides); + return await response.value(); + } + /** */ async allowGuestMessageRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { @@ -766,6 +843,48 @@ export class OpenapiOtherApi extends runtime.BaseAPI { return await response.value(); } + /** + */ + async listFriendRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + + let urlPath = `/api/user/friend/list`; + + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + // CHANGED: Handle all status codes defined in the OpenAPI spec, not just 2xx responses + // This allows typed access to error responses (4xx, 5xx) and other status codes. + // The code routes responses based on the actual HTTP status code and returns + // appropriately typed ApiResponse wrappers for each status code. + if (response.status === 200) { + // Object response for status 200 + return new runtime.JSONApiResponse(response, (jsonValue) => ListFriend200ResponseFromJSON(jsonValue)); + } + if (response.status === 401) { + // Object response for status 401 + return new runtime.JSONApiResponse(response, (jsonValue) => StatusOtp401ResponseFromJSON(jsonValue)); + } + // CHANGED: Throw error if status code is not handled by any of the defined responses + // This ensures all code paths return a value and provides clear error messages for unexpected status codes + // Only throw if responses were defined but none matched the actual status code + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 401`); + } + + /** + */ + async listFriend(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.listFriendRaw(initOverrides); + return await response.value(); + } + /** */ async loginRaw(requestParameters: LoginOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { @@ -1016,6 +1135,60 @@ export class OpenapiOtherApi extends runtime.BaseAPI { return await response.value(); } + /** + */ + async removeFriendRaw(requestParameters: RemoveFriendRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters['user'] == null) { + throw new runtime.RequiredError( + 'user', + 'Required parameter "user" was null or undefined when calling removeFriend().' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + + let urlPath = `/api/user/friend/remove/{user}`; + urlPath = urlPath.replace(`{${"user"}}`, encodeURIComponent(String(requestParameters['user']))); + + const response = await this.request({ + path: urlPath, + method: 'PUT', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + // CHANGED: Handle all status codes defined in the OpenAPI spec, not just 2xx responses + // This allows typed access to error responses (4xx, 5xx) and other status codes. + // The code routes responses based on the actual HTTP status code and returns + // appropriately typed ApiResponse wrappers for each status code. + if (response.status === 200) { + // Object response for status 200 + return new runtime.JSONApiResponse(response, (jsonValue) => RemoveFriend200ResponseFromJSON(jsonValue)); + } + if (response.status === 401) { + // Object response for status 401 + return new runtime.JSONApiResponse(response, (jsonValue) => ChangePassword401ResponseFromJSON(jsonValue)); + } + if (response.status === 404) { + // Object response for status 404 + return new runtime.JSONApiResponse(response, (jsonValue) => RemoveFriend404ResponseFromJSON(jsonValue)); + } + // CHANGED: Throw error if status code is not handled by any of the defined responses + // This ensures all code paths return a value and provides clear error messages for unexpected status codes + // Only throw if responses were defined but none matched the actual status code + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 401, 404`); + } + + /** + */ + async removeFriend(requestParameters: RemoveFriendRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.removeFriendRaw(requestParameters, initOverrides); + return await response.value(); + } + /** */ async signinRaw(requestParameters: SigninRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { diff --git a/frontend/src/api/generated/models/AddFriend200Response.ts b/frontend/src/api/generated/models/AddFriend200Response.ts new file mode 100644 index 0000000..0ce06f7 --- /dev/null +++ b/frontend/src/api/generated/models/AddFriend200Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface AddFriend200Response + */ +export interface AddFriend200Response { + /** + * + * @type {string} + * @memberof AddFriend200Response + */ + kind: AddFriend200ResponseKindEnum; + /** + * + * @type {string} + * @memberof AddFriend200Response + */ + msg: AddFriend200ResponseMsgEnum; +} + + +/** + * @export + */ +export const AddFriend200ResponseKindEnum = { + Success: 'success' +} as const; +export type AddFriend200ResponseKindEnum = typeof AddFriend200ResponseKindEnum[keyof typeof AddFriend200ResponseKindEnum]; + +/** + * @export + */ +export const AddFriend200ResponseMsgEnum = { + AddFriendSuccess: 'addFriend.success' +} as const; +export type AddFriend200ResponseMsgEnum = typeof AddFriend200ResponseMsgEnum[keyof typeof AddFriend200ResponseMsgEnum]; + + +/** + * Check if a given object implements the AddFriend200Response interface. + */ +export function instanceOfAddFriend200Response(value: object): value is AddFriend200Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function AddFriend200ResponseFromJSON(json: any): AddFriend200Response { + return AddFriend200ResponseFromJSONTyped(json, false); +} + +export function AddFriend200ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): AddFriend200Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function AddFriend200ResponseToJSON(json: any): AddFriend200Response { + return AddFriend200ResponseToJSONTyped(json, false); +} + +export function AddFriend200ResponseToJSONTyped(value?: AddFriend200Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/AddFriend404Response.ts b/frontend/src/api/generated/models/AddFriend404Response.ts new file mode 100644 index 0000000..6431f79 --- /dev/null +++ b/frontend/src/api/generated/models/AddFriend404Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface AddFriend404Response + */ +export interface AddFriend404Response { + /** + * + * @type {string} + * @memberof AddFriend404Response + */ + kind: AddFriend404ResponseKindEnum; + /** + * + * @type {string} + * @memberof AddFriend404Response + */ + msg: AddFriend404ResponseMsgEnum; +} + + +/** + * @export + */ +export const AddFriend404ResponseKindEnum = { + Failure: 'failure' +} as const; +export type AddFriend404ResponseKindEnum = typeof AddFriend404ResponseKindEnum[keyof typeof AddFriend404ResponseKindEnum]; + +/** + * @export + */ +export const AddFriend404ResponseMsgEnum = { + AddFriendFailureUnknownUser: 'addFriend.failure.unknownUser' +} as const; +export type AddFriend404ResponseMsgEnum = typeof AddFriend404ResponseMsgEnum[keyof typeof AddFriend404ResponseMsgEnum]; + + +/** + * Check if a given object implements the AddFriend404Response interface. + */ +export function instanceOfAddFriend404Response(value: object): value is AddFriend404Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function AddFriend404ResponseFromJSON(json: any): AddFriend404Response { + return AddFriend404ResponseFromJSONTyped(json, false); +} + +export function AddFriend404ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): AddFriend404Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function AddFriend404ResponseToJSON(json: any): AddFriend404Response { + return AddFriend404ResponseToJSONTyped(json, false); +} + +export function AddFriend404ResponseToJSONTyped(value?: AddFriend404Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/ListFriend200Response.ts b/frontend/src/api/generated/models/ListFriend200Response.ts new file mode 100644 index 0000000..b354192 --- /dev/null +++ b/frontend/src/api/generated/models/ListFriend200Response.ts @@ -0,0 +1,110 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +import type { ListFriend200ResponsePayload } from './ListFriend200ResponsePayload'; +import { + ListFriend200ResponsePayloadFromJSON, + ListFriend200ResponsePayloadFromJSONTyped, + ListFriend200ResponsePayloadToJSON, + ListFriend200ResponsePayloadToJSONTyped, +} from './ListFriend200ResponsePayload'; + +/** + * + * @export + * @interface ListFriend200Response + */ +export interface ListFriend200Response { + /** + * + * @type {string} + * @memberof ListFriend200Response + */ + kind: ListFriend200ResponseKindEnum; + /** + * + * @type {string} + * @memberof ListFriend200Response + */ + msg: ListFriend200ResponseMsgEnum; + /** + * + * @type {ListFriend200ResponsePayload} + * @memberof ListFriend200Response + */ + payload: ListFriend200ResponsePayload; +} + + +/** + * @export + */ +export const ListFriend200ResponseKindEnum = { + Success: 'success' +} as const; +export type ListFriend200ResponseKindEnum = typeof ListFriend200ResponseKindEnum[keyof typeof ListFriend200ResponseKindEnum]; + +/** + * @export + */ +export const ListFriend200ResponseMsgEnum = { + ListFriendSuccess: 'listFriend.success' +} as const; +export type ListFriend200ResponseMsgEnum = typeof ListFriend200ResponseMsgEnum[keyof typeof ListFriend200ResponseMsgEnum]; + + +/** + * Check if a given object implements the ListFriend200Response interface. + */ +export function instanceOfListFriend200Response(value: object): value is ListFriend200Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + if (!('payload' in value) || value['payload'] === undefined) return false; + return true; +} + +export function ListFriend200ResponseFromJSON(json: any): ListFriend200Response { + return ListFriend200ResponseFromJSONTyped(json, false); +} + +export function ListFriend200ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): ListFriend200Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + 'payload': ListFriend200ResponsePayloadFromJSON(json['payload']), + }; +} + +export function ListFriend200ResponseToJSON(json: any): ListFriend200Response { + return ListFriend200ResponseToJSONTyped(json, false); +} + +export function ListFriend200ResponseToJSONTyped(value?: ListFriend200Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + 'payload': ListFriend200ResponsePayloadToJSON(value['payload']), + }; +} + diff --git a/frontend/src/api/generated/models/ListFriend200ResponsePayload.ts b/frontend/src/api/generated/models/ListFriend200ResponsePayload.ts new file mode 100644 index 0000000..51c2fa5 --- /dev/null +++ b/frontend/src/api/generated/models/ListFriend200ResponsePayload.ts @@ -0,0 +1,74 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +import type { ListFriend200ResponsePayloadFriendsInner } from './ListFriend200ResponsePayloadFriendsInner'; +import { + ListFriend200ResponsePayloadFriendsInnerFromJSON, + ListFriend200ResponsePayloadFriendsInnerFromJSONTyped, + ListFriend200ResponsePayloadFriendsInnerToJSON, + ListFriend200ResponsePayloadFriendsInnerToJSONTyped, +} from './ListFriend200ResponsePayloadFriendsInner'; + +/** + * + * @export + * @interface ListFriend200ResponsePayload + */ +export interface ListFriend200ResponsePayload { + /** + * + * @type {Array} + * @memberof ListFriend200ResponsePayload + */ + friends: Array; +} + +/** + * Check if a given object implements the ListFriend200ResponsePayload interface. + */ +export function instanceOfListFriend200ResponsePayload(value: object): value is ListFriend200ResponsePayload { + if (!('friends' in value) || value['friends'] === undefined) return false; + return true; +} + +export function ListFriend200ResponsePayloadFromJSON(json: any): ListFriend200ResponsePayload { + return ListFriend200ResponsePayloadFromJSONTyped(json, false); +} + +export function ListFriend200ResponsePayloadFromJSONTyped(json: any, ignoreDiscriminator: boolean): ListFriend200ResponsePayload { + if (json == null) { + return json; + } + return { + + 'friends': ((json['friends'] as Array).map(ListFriend200ResponsePayloadFriendsInnerFromJSON)), + }; +} + +export function ListFriend200ResponsePayloadToJSON(json: any): ListFriend200ResponsePayload { + return ListFriend200ResponsePayloadToJSONTyped(json, false); +} + +export function ListFriend200ResponsePayloadToJSONTyped(value?: ListFriend200ResponsePayload | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'friends': ((value['friends'] as Array).map(ListFriend200ResponsePayloadFriendsInnerToJSON)), + }; +} + diff --git a/frontend/src/api/generated/models/ListFriend200ResponsePayloadFriendsInner.ts b/frontend/src/api/generated/models/ListFriend200ResponsePayloadFriendsInner.ts new file mode 100644 index 0000000..de5ba72 --- /dev/null +++ b/frontend/src/api/generated/models/ListFriend200ResponsePayloadFriendsInner.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ListFriend200ResponsePayloadFriendsInner + */ +export interface ListFriend200ResponsePayloadFriendsInner { + /** + * + * @type {string} + * @memberof ListFriend200ResponsePayloadFriendsInner + */ + id: string; + /** + * + * @type {string} + * @memberof ListFriend200ResponsePayloadFriendsInner + */ + name: string; +} + +/** + * Check if a given object implements the ListFriend200ResponsePayloadFriendsInner interface. + */ +export function instanceOfListFriend200ResponsePayloadFriendsInner(value: object): value is ListFriend200ResponsePayloadFriendsInner { + if (!('id' in value) || value['id'] === undefined) return false; + if (!('name' in value) || value['name'] === undefined) return false; + return true; +} + +export function ListFriend200ResponsePayloadFriendsInnerFromJSON(json: any): ListFriend200ResponsePayloadFriendsInner { + return ListFriend200ResponsePayloadFriendsInnerFromJSONTyped(json, false); +} + +export function ListFriend200ResponsePayloadFriendsInnerFromJSONTyped(json: any, ignoreDiscriminator: boolean): ListFriend200ResponsePayloadFriendsInner { + if (json == null) { + return json; + } + return { + + 'id': json['id'], + 'name': json['name'], + }; +} + +export function ListFriend200ResponsePayloadFriendsInnerToJSON(json: any): ListFriend200ResponsePayloadFriendsInner { + return ListFriend200ResponsePayloadFriendsInnerToJSONTyped(json, false); +} + +export function ListFriend200ResponsePayloadFriendsInnerToJSONTyped(value?: ListFriend200ResponsePayloadFriendsInner | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'id': value['id'], + 'name': value['name'], + }; +} + diff --git a/frontend/src/api/generated/models/ListFriend404Response.ts b/frontend/src/api/generated/models/ListFriend404Response.ts new file mode 100644 index 0000000..251ea33 --- /dev/null +++ b/frontend/src/api/generated/models/ListFriend404Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface ListFriend404Response + */ +export interface ListFriend404Response { + /** + * + * @type {string} + * @memberof ListFriend404Response + */ + kind: ListFriend404ResponseKindEnum; + /** + * + * @type {string} + * @memberof ListFriend404Response + */ + msg: ListFriend404ResponseMsgEnum; +} + + +/** + * @export + */ +export const ListFriend404ResponseKindEnum = { + Failure: 'failure' +} as const; +export type ListFriend404ResponseKindEnum = typeof ListFriend404ResponseKindEnum[keyof typeof ListFriend404ResponseKindEnum]; + +/** + * @export + */ +export const ListFriend404ResponseMsgEnum = { + RemoveFriendFailureUnknownUser: 'removeFriend.failure.unknownUser' +} as const; +export type ListFriend404ResponseMsgEnum = typeof ListFriend404ResponseMsgEnum[keyof typeof ListFriend404ResponseMsgEnum]; + + +/** + * Check if a given object implements the ListFriend404Response interface. + */ +export function instanceOfListFriend404Response(value: object): value is ListFriend404Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function ListFriend404ResponseFromJSON(json: any): ListFriend404Response { + return ListFriend404ResponseFromJSONTyped(json, false); +} + +export function ListFriend404ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): ListFriend404Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function ListFriend404ResponseToJSON(json: any): ListFriend404Response { + return ListFriend404ResponseToJSONTyped(json, false); +} + +export function ListFriend404ResponseToJSONTyped(value?: ListFriend404Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/RemoveFriend200Response.ts b/frontend/src/api/generated/models/RemoveFriend200Response.ts new file mode 100644 index 0000000..aa3f7d7 --- /dev/null +++ b/frontend/src/api/generated/models/RemoveFriend200Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface RemoveFriend200Response + */ +export interface RemoveFriend200Response { + /** + * + * @type {string} + * @memberof RemoveFriend200Response + */ + kind: RemoveFriend200ResponseKindEnum; + /** + * + * @type {string} + * @memberof RemoveFriend200Response + */ + msg: RemoveFriend200ResponseMsgEnum; +} + + +/** + * @export + */ +export const RemoveFriend200ResponseKindEnum = { + Success: 'success' +} as const; +export type RemoveFriend200ResponseKindEnum = typeof RemoveFriend200ResponseKindEnum[keyof typeof RemoveFriend200ResponseKindEnum]; + +/** + * @export + */ +export const RemoveFriend200ResponseMsgEnum = { + RemoveFriendSuccess: 'removeFriend.success' +} as const; +export type RemoveFriend200ResponseMsgEnum = typeof RemoveFriend200ResponseMsgEnum[keyof typeof RemoveFriend200ResponseMsgEnum]; + + +/** + * Check if a given object implements the RemoveFriend200Response interface. + */ +export function instanceOfRemoveFriend200Response(value: object): value is RemoveFriend200Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function RemoveFriend200ResponseFromJSON(json: any): RemoveFriend200Response { + return RemoveFriend200ResponseFromJSONTyped(json, false); +} + +export function RemoveFriend200ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): RemoveFriend200Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function RemoveFriend200ResponseToJSON(json: any): RemoveFriend200Response { + return RemoveFriend200ResponseToJSONTyped(json, false); +} + +export function RemoveFriend200ResponseToJSONTyped(value?: RemoveFriend200Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/RemoveFriend404Response.ts b/frontend/src/api/generated/models/RemoveFriend404Response.ts new file mode 100644 index 0000000..7987a33 --- /dev/null +++ b/frontend/src/api/generated/models/RemoveFriend404Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface RemoveFriend404Response + */ +export interface RemoveFriend404Response { + /** + * + * @type {string} + * @memberof RemoveFriend404Response + */ + kind: RemoveFriend404ResponseKindEnum; + /** + * + * @type {string} + * @memberof RemoveFriend404Response + */ + msg: RemoveFriend404ResponseMsgEnum; +} + + +/** + * @export + */ +export const RemoveFriend404ResponseKindEnum = { + Failure: 'failure' +} as const; +export type RemoveFriend404ResponseKindEnum = typeof RemoveFriend404ResponseKindEnum[keyof typeof RemoveFriend404ResponseKindEnum]; + +/** + * @export + */ +export const RemoveFriend404ResponseMsgEnum = { + RemoveFriendFailureUnknownUser: 'removeFriend.failure.unknownUser' +} as const; +export type RemoveFriend404ResponseMsgEnum = typeof RemoveFriend404ResponseMsgEnum[keyof typeof RemoveFriend404ResponseMsgEnum]; + + +/** + * Check if a given object implements the RemoveFriend404Response interface. + */ +export function instanceOfRemoveFriend404Response(value: object): value is RemoveFriend404Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function RemoveFriend404ResponseFromJSON(json: any): RemoveFriend404Response { + return RemoveFriend404ResponseFromJSONTyped(json, false); +} + +export function RemoveFriend404ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): RemoveFriend404Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function RemoveFriend404ResponseToJSON(json: any): RemoveFriend404Response { + return RemoveFriend404ResponseToJSONTyped(json, false); +} + +export function RemoveFriend404ResponseToJSONTyped(value?: RemoveFriend404Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/TttHistory200ResponsePayloadDataInner.ts b/frontend/src/api/generated/models/TttHistory200ResponsePayloadDataInner.ts index 72f38ee..972dac5 100644 --- a/frontend/src/api/generated/models/TttHistory200ResponsePayloadDataInner.ts +++ b/frontend/src/api/generated/models/TttHistory200ResponsePayloadDataInner.ts @@ -13,13 +13,13 @@ */ import { mapValues } from '../runtime'; -import type { TttHistory200ResponsePayloadDataInnerPlayerX } from './TttHistory200ResponsePayloadDataInnerPlayerX'; +import type { ListFriend200ResponsePayloadFriendsInner } from './ListFriend200ResponsePayloadFriendsInner'; import { - TttHistory200ResponsePayloadDataInnerPlayerXFromJSON, - TttHistory200ResponsePayloadDataInnerPlayerXFromJSONTyped, - TttHistory200ResponsePayloadDataInnerPlayerXToJSON, - TttHistory200ResponsePayloadDataInnerPlayerXToJSONTyped, -} from './TttHistory200ResponsePayloadDataInnerPlayerX'; + ListFriend200ResponsePayloadFriendsInnerFromJSON, + ListFriend200ResponsePayloadFriendsInnerFromJSONTyped, + ListFriend200ResponsePayloadFriendsInnerToJSON, + ListFriend200ResponsePayloadFriendsInnerToJSONTyped, +} from './ListFriend200ResponsePayloadFriendsInner'; /** * @@ -35,16 +35,16 @@ export interface TttHistory200ResponsePayloadDataInner { gameId: string; /** * - * @type {TttHistory200ResponsePayloadDataInnerPlayerX} + * @type {ListFriend200ResponsePayloadFriendsInner} * @memberof TttHistory200ResponsePayloadDataInner */ - playerX: TttHistory200ResponsePayloadDataInnerPlayerX; + playerX: ListFriend200ResponsePayloadFriendsInner; /** * - * @type {TttHistory200ResponsePayloadDataInnerPlayerX} + * @type {ListFriend200ResponsePayloadFriendsInner} * @memberof TttHistory200ResponsePayloadDataInner */ - playerO: TttHistory200ResponsePayloadDataInnerPlayerX; + playerO: ListFriend200ResponsePayloadFriendsInner; /** * * @type {string} @@ -95,8 +95,8 @@ export function TttHistory200ResponsePayloadDataInnerFromJSONTyped(json: any, ig return { 'gameId': json['gameId'], - 'playerX': TttHistory200ResponsePayloadDataInnerPlayerXFromJSON(json['playerX']), - 'playerO': TttHistory200ResponsePayloadDataInnerPlayerXFromJSON(json['playerO']), + 'playerX': ListFriend200ResponsePayloadFriendsInnerFromJSON(json['playerX']), + 'playerO': ListFriend200ResponsePayloadFriendsInnerFromJSON(json['playerO']), 'date': json['date'], 'outcome': json['outcome'], }; @@ -114,8 +114,8 @@ export function TttHistory200ResponsePayloadDataInnerToJSONTyped(value?: TttHist return { 'gameId': value['gameId'], - 'playerX': TttHistory200ResponsePayloadDataInnerPlayerXToJSON(value['playerX']), - 'playerO': TttHistory200ResponsePayloadDataInnerPlayerXToJSON(value['playerO']), + 'playerX': ListFriend200ResponsePayloadFriendsInnerToJSON(value['playerX']), + 'playerO': ListFriend200ResponsePayloadFriendsInnerToJSON(value['playerO']), 'date': value['date'], 'outcome': value['outcome'], }; diff --git a/frontend/src/api/generated/models/index.ts b/frontend/src/api/generated/models/index.ts index a5a3877..b7e0ef1 100644 --- a/frontend/src/api/generated/models/index.ts +++ b/frontend/src/api/generated/models/index.ts @@ -1,5 +1,7 @@ /* tslint:disable */ /* eslint-disable */ +export * from './AddFriend200Response'; +export * from './AddFriend404Response'; export * from './AllowGuestMessage200Response'; export * from './AllowGuestMessage403Response'; export * from './ChangeDesc200Response'; @@ -37,6 +39,9 @@ export * from './GuestLogin200ResponsePayload'; export * from './GuestLogin400Response'; export * from './GuestLogin500Response'; export * from './GuestLoginRequest'; +export * from './ListFriend200Response'; +export * from './ListFriend200ResponsePayload'; +export * from './ListFriend200ResponsePayloadFriendsInner'; export * from './Login200Response'; export * from './Login202Response'; export * from './Login202ResponsePayload'; @@ -59,6 +64,8 @@ export * from './ProviderList200Response'; export * from './ProviderList200ResponsePayload'; export * from './ProviderList200ResponsePayloadListInner'; export * from './ProviderList200ResponsePayloadListInnerColors'; +export * from './RemoveFriend200Response'; +export * from './RemoveFriend404Response'; export * from './Signin200Response'; export * from './Signin200ResponsePayload'; export * from './Signin400Response'; @@ -81,5 +88,4 @@ export * from './TournamentList404Response'; export * from './TttHistory200Response'; export * from './TttHistory200ResponsePayload'; export * from './TttHistory200ResponsePayloadDataInner'; -export * from './TttHistory200ResponsePayloadDataInnerPlayerX'; export * from './TttHistory404Response'; diff --git a/frontend/src/auth.ts b/frontend/src/auth.ts index 5c97eab..63491fc 100644 --- a/frontend/src/auth.ts +++ b/frontend/src/auth.ts @@ -1,7 +1,7 @@ import { showError } from "@app/toast"; import client from "@app/api"; import cookie from "js-cookie"; -import { ensureWindowState, isNullish } from "@app/utils"; +import { ensureWindowState, isNullish, updateFriendsList } from "@app/utils"; import { handleRoute, navigateTo } from "./routing"; cookie.remove("pkce"); @@ -105,10 +105,11 @@ if (!window.__state._headerProfile) { window.__state._reloadOnAuthChange ??= false; if (!window.__state._reloadOnAuthChange) { - document.addEventListener("ft:userChange", () => { + document.addEventListener("ft:userChange", async () => { // if the last forced auth change is less than 1000 sec old -> we do nothing if (Date.now() - (window.__state.lastAuthChange ?? Date.now()) < 1000) return; + await updateFriendsList(); handleRoute(); }); window.__state._reloadOnAuthChange = true; diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index 1555807..64d64bb 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -291,6 +291,13 @@ div-private { right-12 } +.popup-b-friend { + @apply + absolute + bottom-32 + left-12 +} + .popup-b-game { @apply absolute diff --git a/frontend/src/chat/chat.ts b/frontend/src/chat/chat.ts index e0db9f5..f52ce0b 100644 --- a/frontend/src/chat/chat.ts +++ b/frontend/src/chat/chat.ts @@ -29,6 +29,7 @@ import { windowStateVisable } from "./chatHelperFunctions/windowStateVisable"; import { cmdList } from "./chatHelperFunctions/cmdList"; import { actionBtnTTTGames } from "./chatHelperFunctions/actionBtnTTTGames"; import { showError } from "@app/toast"; +import { actionBtnFriend } from "./chatHelperFunctions/actionBtnFriend"; const MAX_SYSTEM_MESSAGES = 10; let inviteMsgFlag: boolean = false; @@ -38,6 +39,7 @@ let keysPressed: Record = {}; declare module "ft_state" { interface State { chatSock?: Socket; + friendList: { id: string; name: string }[]; } } @@ -81,18 +83,20 @@ const sendButton = document.getElementById("b-send") as HTMLButtonElement; const sendtextbox = document.getElementById( "t-chat-window", ) as HTMLButtonElement; -const systemWindow = document.getElementById("chat-system-box") as HTMLDivElement; +const systemWindow = document.getElementById( + "chat-system-box", +) as HTMLDivElement; function chatKeyToggle() { let anti_flicker_control = false; - const chat_hide_key = 'escape'; - const chat_display_key = 'f2'; - const home_display_key = 'f8'; + const chat_hide_key = "escape"; + const chat_display_key = "f2"; + const home_display_key = "f8"; document.addEventListener("keydown", (event) => { if (event.repeat && keysPressed[chat_hide_key] === true) { anti_flicker_control = true; - return ; - }; + return; + } keysPressed[event.key.toLowerCase()] = true; }); document.addEventListener("keyup", (event) => { @@ -101,34 +105,33 @@ function chatKeyToggle() { anti_flicker_control = false; } }); - setInterval( () => { - if(keysPressed[chat_hide_key] === true) { - overlay.classList.remove("opacity-60"); - chatBox.classList.add("hidden"); - chatMessageIn?.classList.add("hidden"); - chatMessageIn!.textContent = ''; - profilList?.classList.add("hidden"); - windowStateHidden(); + setInterval(() => { + if (keysPressed[chat_hide_key] === true) { + overlay.classList.remove("opacity-60"); + chatBox.classList.add("hidden"); + chatMessageIn?.classList.add("hidden"); + chatMessageIn!.textContent = ""; + profilList?.classList.add("hidden"); + windowStateHidden(); } if (keysPressed[chat_display_key] === true) { - anti_flicker_control = false; - chatBox.classList.remove("hidden"); - overlay.classList.add("opacity-60"); - chatMessageIn?.classList.add("hidden"); - chatMessageIn!.textContent = ''; - let socket = window.__state.chatSock; - if (!socket) return; - connected(socket); - sendtextbox.focus(); - windowStateVisable(); - + anti_flicker_control = false; + chatBox.classList.remove("hidden"); + overlay.classList.add("opacity-60"); + chatMessageIn?.classList.add("hidden"); + chatMessageIn!.textContent = ""; + let socket = window.__state.chatSock; + if (!socket) return; + connected(socket); + sendtextbox.focus(); + windowStateVisable(); } - if (keysPressed[home_display_key] === true) { - navigateTo('/app/'); + if (keysPressed[home_display_key] === true) { + navigateTo("/app/"); quitChat(); } - }, 1000/10); -}; + }, 1000 / 10); +} function initChatSocket() { let socket = getSocket(); @@ -151,9 +154,10 @@ function initChatSocket() { !profilList || !sendButton || !sendtextbox || - !systemWindow - ) return showError("fatal error"); - + !systemWindow + ) + return showError("fatal error"); + // Listen for the 'connect' event socket.on("connect", async () => { await waitSocketConnected(socket); @@ -201,10 +205,10 @@ function initChatSocket() { if (socket) { connected(socket); } - + if (chatWindow && data.message.destination === "") { chatMessageIn?.classList.remove("hidden"); - chatMessageIn!.textContent = '🔵'; + chatMessageIn!.textContent = "🔵"; const messageElement = document.createElement("div"); messageElement.textContent = `${data.message.user}: ${data.message.text}`; chatWindow.appendChild(messageElement); @@ -213,7 +217,7 @@ function initChatSocket() { if (chatWindow && data.message.destination === "privateMsg") { chatMessageIn?.classList.remove("hidden"); - chatMessageIn!.textContent = '🔴'; + chatMessageIn!.textContent = "🔴"; const messageElement = document.createElement("div-private"); messageElement.textContent = `🔒${data.message.user}: ${data.message.text}`; chatWindow.appendChild(messageElement); @@ -222,7 +226,7 @@ function initChatSocket() { if (chatWindow && data.message.destination === "inviteMsg") { chatMessageIn?.classList.remove("hidden"); - chatMessageIn!.textContent = '🟢'; + chatMessageIn!.textContent = "🟢"; const messageElement = document.createElement("div-private"); const chatWindow = document.getElementById( "t-chatbox", @@ -235,14 +239,13 @@ function initChatSocket() { if (systemWindow && data.message.destination === "system-info") { const messageElement = document.createElement("div"); messageElement.textContent = `${data.message.user}: ${data.message.text}`; - + // keep only last 10 while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) { systemWindow.removeChild(systemWindow.firstChild!); } systemWindow.appendChild(messageElement); systemWindow.lastElementChild?.scrollIntoView({ block: "end" }); - } }); @@ -256,6 +259,7 @@ function initChatSocket() { actionBtnPopUpBlock(profil, socket); actionBtnPongGames(profil, socket); actionBtnTTTGames(profil, socket); + actionBtnFriend(profil, socket); }); socket.on("blockUser", (blocked: ClientProfil) => { @@ -276,9 +280,9 @@ function initChatSocket() { if (blockUserBtn) { let message = ""; if (data.userState === "block") { - (message = "un-block"); + message = "un-block"; } else { - (message = "block"); + message = "block"; } blockUserBtn.textContent = message; } @@ -292,17 +296,15 @@ function initChatSocket() { const htmlBaliseRegex = /]*>[\s\S]*?<\/a>/; const htmlBaliseMatch = message.match(htmlBaliseRegex); - if (htmlBaliseMatch) - addInviteMessage(message); - else - addMessage(message); + if (htmlBaliseMatch) addInviteMessage(message); + else addMessage(message); }); //receives broadcast of the next GAME socket.on("nextGame", (message: string) => { openMessagePopup(message); }); - + //receives broadcast of the next GAME socket.on("tourStatus", (message: string) => { openMessagePopup(message); @@ -389,20 +391,20 @@ sendButton?.addEventListener("click", () => { } break; - case "@pong": + case "@pong": if (msgCommand[1] === "") { navigateTo("/app/pong/games"); quitChat(); - } + } break; - case "@ttt": + case "@ttt": if (msgCommand[1] === "") { navigateTo("/app/ttt/games"); quitChat(); - } + } break; - + case "@guest": if (!userId) { return; @@ -503,7 +505,6 @@ clearText?.addEventListener("click", () => { bquit?.addEventListener("click", () => { quitChat(); - }); myGames?.addEventListener("click", () => { @@ -525,7 +526,7 @@ sendtextbox.addEventListener("keydown", (event) => { } }); -chatButton!.addEventListener("click",() => { +chatButton!.addEventListener("click", () => { if (chatBox.classList.contains("hidden")) { chatBox.classList.toggle("hidden"); overlay.classList.add("opacity-60"); @@ -534,14 +535,14 @@ chatButton!.addEventListener("click",() => { if (!socket) return; connected(socket); chatMessageIn?.classList.add("hidden"); - chatMessageIn!.textContent = ''; - sendtextbox.focus(); + chatMessageIn!.textContent = ""; + sendtextbox.focus(); } else { chatBox.classList.toggle("hidden"); overlay.classList.remove("opacity-60"); windowStateHidden(); chatMessageIn?.classList.add("hidden"); - chatMessageIn!.textContent = ''; + chatMessageIn!.textContent = ""; } }); diff --git a/frontend/src/chat/chatHelperFunctions/actionBtnFriend.ts b/frontend/src/chat/chatHelperFunctions/actionBtnFriend.ts new file mode 100644 index 0000000..8ca45ec --- /dev/null +++ b/frontend/src/chat/chatHelperFunctions/actionBtnFriend.ts @@ -0,0 +1,35 @@ +import client from "@app/api"; +import type { ClientProfil } from "../types_front"; +import { Socket } from "socket.io-client"; +import { showError, showSuccess } from "@app/toast"; +import { getFriendList, updateFriendsList } from "@app/utils"; + +/** + * function listens for a click on the TTT game History button + * @param profile - Clients target profil + * @param senderSocket - socket from the sender +**/ + +export function actionBtnFriend(profile: ClientProfil, senderSocket: Socket) { + setTimeout(() => { + const friend = document.querySelector("#btn-friend"); + friend?.addEventListener("click", async () => { + let friendList = getFriendList(); + if (!friendList.some(v => v.id === profile.userID!)) { + let req = await client.addFriend({ user: profile.userID! }); + if (req.kind === 'success') + showSuccess('Successfully added a new Friend') + else + showError('Failed to add a new Friend'); + } + else { + let req = await client.removeFriend({ user: profile.userID! }); + if (req.kind === 'success') + showSuccess('Successfully removed a Friend') + else + showError('Failed to remove a Friend'); + } + await updateFriendsList(); + }); + }, 0) +}; diff --git a/frontend/src/chat/chatHelperFunctions/openProfilePopup.ts b/frontend/src/chat/chatHelperFunctions/openProfilePopup.ts index fcef7e9..f99e4cf 100644 --- a/frontend/src/chat/chatHelperFunctions/openProfilePopup.ts +++ b/frontend/src/chat/chatHelperFunctions/openProfilePopup.ts @@ -6,17 +6,18 @@ export async function openProfilePopup(profil: ClientProfil) { modalname.innerHTML = `
- Profile of ${profil.user}
+ Profile of ${profil.user} Login status: ${profil.loginName ?? 'Guest'}
- Login ID: ${profil.userID ?? ''} + Login ID: ${profil.userID ?? ''} +
About: ${profil.text}
- + `; const profilList = document.getElementById("profile-modal") ?? null; diff --git a/frontend/src/pages/friendList/friendList.html b/frontend/src/pages/friendList/friendList.html new file mode 100644 index 0000000..68f64db --- /dev/null +++ b/frontend/src/pages/friendList/friendList.html @@ -0,0 +1,9 @@ +
+
+

+ FriendList +

+
+
+
diff --git a/frontend/src/pages/friendList/friendList.ts b/frontend/src/pages/friendList/friendList.ts new file mode 100644 index 0000000..83fb2df --- /dev/null +++ b/frontend/src/pages/friendList/friendList.ts @@ -0,0 +1,41 @@ +import { addRoute, navigateTo, setTitle, type RouteHandlerParams, type RouteHandlerReturn } from "@app/routing"; +import page from './friendList.html?raw'; +import { getFriendList, isNullish, updateFriendsList } from "@app/utils"; +import client from "@app/api"; +import { updateUser } from "@app/auth"; +import { showError } from "@app/toast"; + + +async function friends(_url: string, args: RouteHandlerParams): Promise { + setTitle("Tic Tac Toe Games"); + let user = await updateUser(); + if (isNullish(user)) { + return { html: ' You aren\'t logged in ', postInsert: () => { showError("You must be logged in !"); navigateTo("/") } }; + } + await updateFriendsList(); + let friendList = getFriendList(); + friendList.sort(); + + let friendsElem = friendList.map(g => { + let e = document.createElement('div'); + e.className = 'grid grid-cols-[1fr_auto_1fr] items-center bg-zinc-800 rounded-lg px-4 py-3'; + + e.innerHTML = ` +
${g.name}
+ TTT Games + Pong Games + `; + return e; + }).filter(v => !isNullish(v)); + + return { + html: page, postInsert: async (app) => { + if (!app) return; + const friendsBox = app.querySelector("#friendList"); + if (!friendsBox) return; + friendsElem.forEach(c => friendsBox.appendChild(c)); + } + }; +} + +addRoute('/friends', friends); diff --git a/frontend/src/pages/index.ts b/frontend/src/pages/index.ts index cd6c41d..aef2743 100644 --- a/frontend/src/pages/index.ts +++ b/frontend/src/pages/index.ts @@ -1,6 +1,5 @@ import { setTitle, handleRoute } from '@app/routing'; import './root/root.ts' -import '../chat/chat.ts' import './pong/pong.ts' import './login/login.ts' import './signin/signin.ts' @@ -10,6 +9,7 @@ import './logout/logout.ts' import './pongHistory/pongHistory.ts' import './tttHistory/tttHistory.ts' import './tourHistory/tourHistory.ts' +import './friendList/friendList.ts' // ---- Initial load ---- setTitle(""); diff --git a/frontend/src/pages/pong/pong.ts b/frontend/src/pages/pong/pong.ts index 3dd40ed..d8c09e3 100644 --- a/frontend/src/pages/pong/pong.ts +++ b/frontend/src/pages/pong/pong.ts @@ -107,7 +107,9 @@ function tourinfoButtons(tourInfo : HTMLButtonElement, tourScoreScreen : HTMLDiv }); } -function gameJoinButtons(socket : CSocket, inTournament : boolean, currentGame : currentGameInfo | null, +let inTournament: boolean = false; + +function gameJoinButtons(socket : CSocket, currentGame : currentGameInfo | null, tournament : HTMLButtonElement, queue : HTMLButtonElement, localGame : HTMLButtonElement, ready : HTMLButtonElement) { tournament.addEventListener("click", () => { @@ -150,6 +152,10 @@ function gameJoinButtons(socket : CSocket, inTournament : boolean, currentGame : } }); localGame.addEventListener("click", () => { + if (inTournament) { + showError("You can't queue up currently !"); + return; + } if ( queue.innerText !== QueueState.Iddle || currentGame !== null || @@ -274,7 +280,7 @@ function pongClient( setTitle("Pong Game"); const urlParams = new URLSearchParams(window.location.search); let game_req_join = urlParams.get("game"); - let inTournament = false; + inTournament = false; return { html: authHtml, @@ -548,7 +554,7 @@ function pongClient( setInterval(() => {keys_listen_setup(currentGame, socket, keys, playHow, playHow_b, tourScoreScreen, queue)}, 1000 / 60); - gameJoinButtons(socket, inTournament, currentGame, tournament, queue, localGame, ready); + gameJoinButtons(socket, currentGame, tournament, queue, localGame, ready); playhowButtons(playHow_b, playHow); tourinfoButtons(tourInfo, tourScoreScreen); diff --git a/frontend/src/pages/profile/profile.html b/frontend/src/pages/profile/profile.html index 5dfa4f2..b24ab6d 100644 --- a/frontend/src/pages/profile/profile.html +++ b/frontend/src/pages/profile/profile.html @@ -31,6 +31,19 @@ + +
@@ -52,8 +65,7 @@ - +