Invite and Session API/Services; Home view; Sidebar Items; Modals

This commit is contained in:
Aslan 2026-01-10 19:24:10 -05:00
parent 280158470a
commit e36587b99d
80 changed files with 1343 additions and 71 deletions

View file

@ -31,7 +31,7 @@ const updateChannelApi = async (
const removeChannelApi = async (
request: IRemoveChannelRequest,
): Promise<IRemoveChannelResponse> => {
return await callApi(HTTP.DELETE, `channel/${request.id}`, request);
return await callApi(HTTP.DELETE, `channel/${request.id}`);
};
export {

View file

@ -22,6 +22,7 @@ interface ICreateChannelResponse extends IFetchChannel {}
interface IUpdateChannelRequest {
id: string;
name?: string;
description?: string;
}
interface IUpdateChannelResponse extends IFetchChannel {}

View file

@ -2,12 +2,20 @@ import { callApi, HTTP } from "../tools";
import {
IFetchCommunityRequest,
IFetchCommunityResponse,
ICreateCommunityRequest,
ICreateCommunityResponse,
IUpdateCommunityRequest,
IUpdateCommunityResponse,
IRemoveCommunityRequest,
IRemoveCommunityResponse,
IFetchCommunityChannelsRequest,
IFetchCommunityChannelsResponse,
IFetchCommunityRolesRequest,
IFetchCommunityRolesResponse,
IFetchCommunityMembersRequest,
IFetchCommunityMembersResponse,
IFetchCommunityInvitesRequest,
IFetchCommunityInvitesResponse,
} from "./types";
const fetchCommunityApi = async (
@ -16,6 +24,24 @@ const fetchCommunityApi = async (
return await callApi(HTTP.GET, `community/${request.id}`);
};
const createCommunityApi = async (
request: ICreateCommunityRequest,
): Promise<ICreateCommunityResponse> => {
return await callApi(HTTP.POST, `community`, request);
};
const updateCommunityApi = async (
request: IUpdateCommunityRequest,
): Promise<IUpdateCommunityResponse> => {
return await callApi(HTTP.PATCH, `community/${request.id}`, request);
};
const removeCommunityApi = async (
request: IRemoveCommunityRequest,
): Promise<IRemoveCommunityResponse> => {
return await callApi(HTTP.DELETE, `community/${request.id}`);
};
const fetchCommunityChannelsApi = async (
request: IFetchCommunityChannelsRequest,
): Promise<IFetchCommunityChannelsResponse> => {
@ -34,9 +60,19 @@ const fetchCommunityMembersApi = async (
return await callApi(HTTP.GET, `community/${request.id}/members`);
};
const fetchCommunityInvitesApi = async (
request: IFetchCommunityInvitesRequest,
): Promise<IFetchCommunityInvitesResponse> => {
return await callApi(HTTP.GET, `community/${request.id}/invites`);
};
export {
fetchCommunityApi,
createCommunityApi,
updateCommunityApi,
removeCommunityApi,
fetchCommunityChannelsApi,
fetchCommunityRolesApi,
fetchCommunityMembersApi,
fetchCommunityInvitesApi,
};

View file

@ -12,6 +12,28 @@ interface IFetchCommunityRequest {
interface IFetchCommunityResponse extends IFetchCommunity {}
interface ICreateCommunityRequest {
name: string;
}
interface ICreateCommunityResponse extends IFetchCommunity {}
interface IUpdateCommunityRequest {
id: string;
name?: string;
description?: string;
}
interface IUpdateCommunityResponse extends IFetchCommunity {}
interface IRemoveCommunityRequest {
id: string;
}
interface IRemoveCommunityResponse {
id: string;
}
interface IFetchCommunityChannelsRequest {
id: string;
}
@ -54,10 +76,29 @@ interface IFetchCommunityMember {
username: string;
}
interface IFetchCommunityInvitesRequest {
id: string;
}
interface IFetchCommunityInvitesResponse {
id: string;
invites: IFetchCommunityInvite[];
}
interface IFetchCommunityInvite {
id: string;
}
export {
type IFetchCommunity,
type IFetchCommunityRequest,
type IFetchCommunityResponse,
type ICreateCommunityRequest,
type ICreateCommunityResponse,
type IUpdateCommunityRequest,
type IUpdateCommunityResponse,
type IRemoveCommunityRequest,
type IRemoveCommunityResponse,
type IFetchCommunityChannelsRequest,
type IFetchCommunityChannelsResponse,
type IFetchCommunityChannel,
@ -67,4 +108,7 @@ export {
type IFetchCommunityMembersRequest,
type IFetchCommunityMembersResponse,
type IFetchCommunityMember,
type IFetchCommunityInvitesRequest,
type IFetchCommunityInvitesResponse,
type IFetchCommunityInvite,
};

2
src/api/invite/index.ts Normal file
View file

@ -0,0 +1,2 @@
export * from "./invite";
export * from "./types";

29
src/api/invite/invite.ts Normal file
View file

@ -0,0 +1,29 @@
import { callApi, HTTP } from "../tools";
import {
IFetchInviteRequest,
IFetchInviteResponse,
IRemoveInviteRequest,
IRemoveInviteResponse,
IAcceptInviteRequest,
IAcceptInviteResponse,
} from "./types";
const fetchInviteApi = async (
request: IFetchInviteRequest,
): Promise<IFetchInviteResponse> => {
return await callApi(HTTP.GET, `invite/${request.id}`);
};
const removeInviteApi = async (
request: IRemoveInviteRequest,
): Promise<IRemoveInviteResponse> => {
return await callApi(HTTP.DELETE, `invite/${request.id}`);
};
const acceptInviteApi = async (
request: IAcceptInviteRequest,
): Promise<IAcceptInviteResponse> => {
return await callApi(HTTP.GET, `invite/${request.id}/accept`);
};
export { fetchInviteApi, removeInviteApi, acceptInviteApi };

48
src/api/invite/types.ts Normal file
View file

@ -0,0 +1,48 @@
interface IFetchInvite {
id: string;
communityId: string;
valid: boolean;
unlimitedInvites: boolean;
hasExpiration: boolean;
totalInvites: number;
remainingInvites: number;
creationDate: number;
expirationDate: number;
}
interface IFetchInviteRequest {
id: string;
}
interface IFetchInviteResponse extends IFetchInvite {}
interface IRemoveInviteRequest {
id: string;
}
interface IRemoveInviteResponse {
id: string;
userId: string;
}
interface IAcceptInviteRequest {
id: string;
}
interface IAcceptInviteResponse {
id: string;
userId: string;
userName: string;
communityId: string;
communityName: string;
}
export {
type IFetchInvite,
type IFetchInviteRequest,
type IFetchInviteResponse,
type IRemoveInviteRequest,
type IRemoveInviteResponse,
type IAcceptInviteRequest,
type IAcceptInviteResponse,
};

View file

@ -31,7 +31,7 @@ const updateRoleApi = async (
const removeRoleApi = async (
request: IRemoveRoleRequest,
): Promise<IRemoveRoleResponse> => {
return await callApi(HTTP.DELETE, `role/${request.id}`, request);
return await callApi(HTTP.DELETE, `role/${request.id}`);
};
export { fetchRoleApi, createRoleApi, updateRoleApi, removeRoleApi };

2
src/api/session/index.ts Normal file
View file

@ -0,0 +1,2 @@
export * from "./session";
export * from "./types";

View file

@ -0,0 +1,21 @@
import { callApi, HTTP } from "../tools";
import {
IFetchSessionRequest,
IFetchSessionResponse,
IRemoveSessionRequest,
IRemoveSessionResponse,
} from "./types";
const fetchSessionApi = async (
request: IFetchSessionRequest,
): Promise<IFetchSessionResponse> => {
return await callApi(HTTP.GET, `session/${request.id}`);
};
const removeSessionApi = async (
request: IRemoveSessionRequest,
): Promise<IRemoveSessionResponse> => {
return await callApi(HTTP.DELETE, `session/${request.id}`);
};
export { fetchSessionApi, removeSessionApi };

28
src/api/session/types.ts Normal file
View file

@ -0,0 +1,28 @@
interface IFetchSession {
id: string;
userId: string;
creationDate: number;
}
interface IFetchSessionRequest {
id: string;
}
interface IFetchSessionResponse extends IFetchSession {}
interface IRemoveSessionRequest {
id: string;
}
interface IRemoveSessionResponse {
id: string;
userId: string;
}
export {
type IFetchSession,
type IFetchSessionRequest,
type IFetchSessionResponse,
type IRemoveSessionRequest,
type IRemoveSessionResponse,
};

View file

@ -18,6 +18,20 @@ interface IFetchUserRequest {
interface IFetchUserResponse extends IFetchUser {}
interface IFetchUserSessionsRequest {
id: string;
}
interface IFetchUserSessionsResponse {
id: string;
sessions: IFetchUserSession[];
}
interface IFetchUserSession {
id: string;
userId: string;
}
interface IFetchUserCommunitiesRequest {
id: string;
}
@ -30,7 +44,6 @@ interface IFetchUserCommunitiesResponse {
interface IFetchUserCommunity {
id: string;
name: string;
description: string;
}
export {
@ -38,6 +51,9 @@ export {
type IFetchLoggedUserResponse,
type IFetchUserRequest,
type IFetchUserResponse,
type IFetchUserSessionsRequest,
type IFetchUserSessionsResponse,
type IFetchUserSession,
type IFetchUserCommunitiesRequest,
type IFetchUserCommunitiesResponse,
type IFetchUserCommunity,

View file

@ -3,6 +3,8 @@ import {
IFetchLoggedUserResponse,
IFetchUserRequest,
IFetchUserResponse,
IFetchUserSessionsRequest,
IFetchUserSessionsResponse,
IFetchUserCommunitiesRequest,
IFetchUserCommunitiesResponse,
} from "./types";
@ -17,10 +19,21 @@ const fetchUserApi = async (
return await callApi(HTTP.GET, `user/${request.id}`);
};
const fetchUserSessionsApi = async (
request: IFetchUserSessionsRequest,
): Promise<IFetchUserSessionsResponse> => {
return await callApi(HTTP.GET, `user/${request.id}/sessions`);
};
const fetchUserCommunitiesApi = async (
request: IFetchUserCommunitiesRequest,
): Promise<IFetchUserCommunitiesResponse> => {
return await callApi(HTTP.GET, `user/${request.id}/communities`);
};
export { fetchLoggedUserApi, fetchUserApi, fetchUserCommunitiesApi };
export {
fetchLoggedUserApi,
fetchUserApi,
fetchUserSessionsApi,
fetchUserCommunitiesApi,
};

View file

@ -5,7 +5,7 @@ const Channel: Component<IChannelProps> = (props: IChannelProps) => {
return (
<li
class={`flex flex-row gap-2 items-center p-1 cursor-pointer rounded-lg ${props.active ? "bg-stone-700 hover:bg-stone-700" : "hover:bg-stone-800"}`}
onClick={() => props.onChannelClick(props.id)}
onClick={() => props.onChannelClick?.(props.id)}
>
<div class="font-bold text-xl">&nbsp#</div>
<div class="font-bold">{props.name}</div>

View file

@ -2,7 +2,7 @@ interface IChannelProps {
id: string;
name: string;
active: boolean;
onChannelClick: (id: string) => void;
onChannelClick?: (id: string) => void;
}
export { type IChannelProps };

View file

@ -5,10 +5,10 @@ const Community: Component<ICommunityProps> = (props: ICommunityProps) => {
return (
<div
class="avatar cursor-pointer"
onClick={() => props.onCommunityClick(props.id)}
onClick={() => props.onCommunityClick?.(props.id)}
>
<div
class={`w-full transition-[border-radius] duration-300 outline-stone-300 hover:outline-2 ${props.active ? "rounded-lg outline-2" : "rounded-4xl"}`}
class={`w-full transition-[border-radius] duration-300 outline-stone-300 ${props.active ? "rounded-lg outline-3 hover:outline-3" : "rounded-4xl hover:outline-2"}`}
>
<img src={props.avatar} />
</div>

View file

@ -3,7 +3,7 @@ interface ICommunityProps {
name: string;
avatar: string;
active: boolean;
onCommunityClick: (id: string) => void;
onCommunityClick?: (id: string) => void;
}
export { type ICommunityProps };

View file

@ -0,0 +1,101 @@
import { createSignal, type Component } from "solid-js";
import { ICommunityModalProps } from "./types";
import { dispatch } from "../../store/state";
import { CommunityActionTypes } from "../../store/community";
import { InviteActionTypes } from "../../store/invite";
const CommunityModal: Component<ICommunityModalProps> = (props) => {
const [getCommunityName, setCommunityName] = createSignal<string>("");
const [getInviteId, setInviteId] = createSignal<string>("");
const onCreateCommunity = () => {
dispatch({
type: CommunityActionTypes.CREATE_COMMUNITY_START,
payload: {
name: getCommunityName(),
},
});
setCommunityName("");
props.onClose?.();
};
const onJoinCommunity = () => {
dispatch({
type: InviteActionTypes.ACCEPT_INVITE_START,
payload: getInviteId(),
});
setInviteId("");
props.onClose?.();
};
const createCommunityHtml = () => (
<>
<h3 class="text-lg font-bold text-center mb-6">
Create a new Community
</h3>
<div class="bg-stone-800 h-16 p-2 flex flex-row gap-2 rounded-2xl">
<label class="bg-stone-800 input w-full h-full rounded-xl focus:border-none outline-none">
<input
type="text"
placeholder="Enter name of the new community"
value={getCommunityName()}
onInput={(e) => setCommunityName(e.currentTarget.value)}
/>
</label>
<button
class="bg-stone-950 btn btn-neutral h-full rounded-xl"
onClick={onCreateCommunity}
>
Create
</button>
</div>
</>
);
const joinCommunityHtml = () => (
<>
<h3 class="text-lg font-bold text-center mb-6">
Join an existing Community
</h3>
<div class="bg-stone-800 h-16 p-2 flex flex-row gap-2 rounded-2xl">
<label class="bg-stone-800 input w-full h-full rounded-xl focus:border-none outline-none">
<input
type="text"
placeholder="Enter invite ID"
value={getInviteId()}
onInput={(e) => setInviteId(e.currentTarget.value)}
/>
</label>
<button
class="bg-stone-950 btn btn-neutral h-full rounded-xl"
onClick={onJoinCommunity}
>
Join
</button>
</div>
</>
);
return (
<div>
<dialog ref={props.dialogRef} class="modal bg-[#00000050]">
<div class="modal-box bg-stone-950 rounded-3xl">
{createCommunityHtml()}
<div class="divider my-8"></div>
{joinCommunityHtml()}
</div>
<form
onClick={props.onClose}
method="dialog"
class="modal-backdrop"
></form>
</dialog>
</div>
);
};
export default CommunityModal;

View file

@ -0,0 +1,2 @@
export * from "./CommunityModal";
export * from "./types";

View file

@ -0,0 +1,6 @@
interface ICommunityModalProps {
dialogRef?: (element: HTMLDialogElement) => void;
onClose?: () => void;
}
export { type ICommunityModalProps };

View file

@ -0,0 +1,22 @@
import type { Component } from "solid-js";
import { IHomeCardProps } from "./types";
import { Dynamic } from "solid-js/web";
const HomeCard: Component<IHomeCardProps> = (props: IHomeCardProps) => {
return (
<a class="w-60 cursor-pointer" onClick={props.onClick}>
<div class="card border-2 bg-stone-800 border-stone-500 hover:border-stone-100 w-60 h-60">
<div class="flex flex-col h-full gap-1 m-6">
<div class="w-20">
<Dynamic component={props.icon} />
</div>
<div class="grow"></div>
<h2 class="card-title">{props.title}</h2>
<p>{props.description}</p>
</div>
</div>
</a>
);
};
export { HomeCard };

View file

@ -0,0 +1,2 @@
export * from "./HomeCard";
export * from "./types";

View file

@ -0,0 +1,11 @@
import { JSXElement } from "solid-js";
import { IconParameters } from "../icons";
interface IHomeCardProps {
title: string;
description: string;
onClick?: () => void;
icon: (props: IconParameters) => JSXElement;
}
export { type IHomeCardProps };

View file

@ -5,7 +5,7 @@ const Member: Component<IMemberProps> = (props: IMemberProps) => {
return (
<li
class={`flex flex-row gap-4 items-center p-1 cursor-pointer rounded-lg ${props.active ? "bg-stone-700 hover:bg-stone-700" : "hover:bg-stone-800"}`}
onClick={() => props.onMemberClick(props.id)}
onClick={() => props.onMemberClick?.(props.id)}
>
<div class="avatar">
<div class="w-9 rounded-full">

View file

@ -3,7 +3,7 @@ interface IMemberProps {
username: string;
avatar: string;
active: boolean;
onMemberClick: (id: string) => void;
onMemberClick?: (id: string) => void;
}
export { type IMemberProps };

View file

@ -6,7 +6,7 @@ const Message: Component<IMessageProps> = (props: IMessageProps) => {
<li class="list-row p-3 hover:bg-stone-700">
<div
class="avatar cursor-pointer"
onClick={() => props.onProfileClick(props.userId)}
onClick={() => props.onProfileClick?.(props.userId)}
>
<div class="w-10 rounded-full">
<img src={props.avatar} />

View file

@ -4,7 +4,7 @@ interface IMessageProps {
userId: string;
username: string;
avatar: string;
onProfileClick: (userId: string) => void;
onProfileClick?: (userId: string) => void;
}
export { type IMessageProps };

View file

@ -1,5 +1,4 @@
import type { Component } from "solid-js";
import { state } from "../../store/state";
const MessageBar: Component = () => {
return (
@ -8,7 +7,7 @@ const MessageBar: Component = () => {
<label class="bg-stone-800/50 backdrop-blur-lg input w-full h-full rounded-full focus:border-none outline-none">
<input type="text" placeholder="Send a message..." />
</label>
<button class="bg-black/50 backdrop-blur-lg btn btn-neutral h-full rounded-full">
<button class="bg-stone-950/50 backdrop-blur-lg btn btn-neutral h-full rounded-full">
Send
</button>
</div>

View file

@ -0,0 +1,22 @@
import type { Component } from "solid-js";
import { ISettingsModalProps } from "./types";
const SettingsModal: Component<ISettingsModalProps> = (props) => {
return (
<div>
<dialog ref={props.dialogRef} class="modal bg-[#00000050]">
<div class="modal-box bg-stone-950 rounded-3xl">
<h3 class="text-lg font-bold text-center">Settings</h3>
<p class="py-4 text-center">Not implemented yet</p>
</div>
<form
onClick={props.onClose}
method="dialog"
class="modal-backdrop"
></form>
</dialog>
</div>
);
};
export default SettingsModal;

View file

@ -0,0 +1,2 @@
export * from "./SettingsModal";
export * from "./types";

View file

@ -0,0 +1,6 @@
interface ISettingsModalProps {
dialogRef?: (element: HTMLDialogElement) => void;
onClose?: () => void;
}
export { type ISettingsModalProps };

View file

@ -0,0 +1,18 @@
import type { Component } from "solid-js";
import { ISidebarItemProps } from "./types";
import { Dynamic } from "solid-js/web";
const SidebarItem: Component<ISidebarItemProps> = (
props: ISidebarItemProps,
) => {
return (
<div
class={`bg-stone-800 w-full p-2 cursor-pointer transition-[border-radius] duration-300 outline-stone-300 ${props.active ? "rounded-lg outline-3 hover:outline-3" : "rounded-4xl hover:outline-2"}`}
onClick={() => props.onClick?.()}
>
<Dynamic component={props.icon} />
</div>
);
};
export { SidebarItem };

View file

@ -0,0 +1,2 @@
export * from "./SidebarItem";
export * from "./types";

View file

@ -0,0 +1,10 @@
import { JSXElement } from "solid-js";
import { IconParameters } from "../icons";
interface ISidebarItemProps {
icon: (props: IconParameters) => JSXElement;
active: boolean;
onClick?: () => void;
}
export { type ISidebarItemProps };

View file

@ -0,0 +1,28 @@
import type { Component } from "solid-js";
import { IconParameters, defaultFillIconParameters as defaults } from "./types";
const HomeIcon: Component<IconParameters> = ({
width,
height,
fill = defaults.fill,
stroke = defaults.stroke,
strokeWidth = defaults.strokeWidth,
}: IconParameters) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
fill={fill}
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke={stroke}
>
<path d="M11.47 3.841a.75.75 0 0 1 1.06 0l8.69 8.69a.75.75 0 1 0 1.06-1.061l-8.689-8.69a2.25 2.25 0 0 0-3.182 0l-8.69 8.69a.75.75 0 1 0 1.061 1.06l8.69-8.689Z" />
<path d="m12 5.432 8.159 8.159c.03.03.06.058.091.086v6.198c0 1.035-.84 1.875-1.875 1.875H15a.75.75 0 0 1-.75-.75v-4.5a.75.75 0 0 0-.75-.75h-3a.75.75 0 0 0-.75.75V21a.75.75 0 0 1-.75.75H5.625a1.875 1.875 0 0 1-1.875-1.875v-6.198a2.29 2.29 0 0 0 .091-.086L12 5.432Z" />
</svg>
);
};
export default HomeIcon;

View file

@ -0,0 +1,31 @@
import type { Component } from "solid-js";
import { IconParameters, defaultFillIconParameters as defaults } from "./types";
const PlusIcon: Component<IconParameters> = ({
width,
height,
fill = defaults.fill,
stroke = defaults.stroke,
strokeWidth = defaults.strokeWidth,
}: IconParameters) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
fill={fill}
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke={stroke}
>
<path
fill-rule="evenodd"
d="M12 3.75a.75.75 0 0 1 .75.75v6.75h6.75a.75.75 0 0 1 0 1.5h-6.75v6.75a.75.75 0 0 1-1.5 0v-6.75H4.5a.75.75 0 0 1 0-1.5h6.75V4.5a.75.75 0 0 1 .75-.75Z"
clip-rule="evenodd"
/>
</svg>
);
};
export default PlusIcon;

View file

@ -0,0 +1,31 @@
import type { Component } from "solid-js";
import { IconParameters, defaultFillIconParameters as defaults } from "./types";
const SettingsIcon: Component<IconParameters> = ({
width,
height,
fill = defaults.fill,
stroke = defaults.stroke,
strokeWidth = defaults.strokeWidth,
}: IconParameters) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
fill={fill}
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke={stroke}
>
<path
fill-rule="evenodd"
d="M11.078 2.25c-.917 0-1.699.663-1.85 1.567L9.05 4.889c-.02.12-.115.26-.297.348a7.493 7.493 0 0 0-.986.57c-.166.115-.334.126-.45.083L6.3 5.508a1.875 1.875 0 0 0-2.282.819l-.922 1.597a1.875 1.875 0 0 0 .432 2.385l.84.692c.095.078.17.229.154.43a7.598 7.598 0 0 0 0 1.139c.015.2-.059.352-.153.43l-.841.692a1.875 1.875 0 0 0-.432 2.385l.922 1.597a1.875 1.875 0 0 0 2.282.818l1.019-.382c.115-.043.283-.031.45.082.312.214.641.405.985.57.182.088.277.228.297.35l.178 1.071c.151.904.933 1.567 1.85 1.567h1.844c.916 0 1.699-.663 1.85-1.567l.178-1.072c.02-.12.114-.26.297-.349.344-.165.673-.356.985-.57.167-.114.335-.125.45-.082l1.02.382a1.875 1.875 0 0 0 2.28-.819l.923-1.597a1.875 1.875 0 0 0-.432-2.385l-.84-.692c-.095-.078-.17-.229-.154-.43a7.614 7.614 0 0 0 0-1.139c-.016-.2.059-.352.153-.43l.84-.692c.708-.582.891-1.59.433-2.385l-.922-1.597a1.875 1.875 0 0 0-2.282-.818l-1.02.382c-.114.043-.282.031-.449-.083a7.49 7.49 0 0 0-.985-.57c-.183-.087-.277-.227-.297-.348l-.179-1.072a1.875 1.875 0 0 0-1.85-1.567h-1.843ZM12 15.75a3.75 3.75 0 1 0 0-7.5 3.75 3.75 0 0 0 0 7.5Z"
clip-rule="evenodd"
/>
</svg>
);
};
export default SettingsIcon;

View file

@ -0,0 +1,7 @@
import HomeIcon from "./HomeIcon";
import SettingsIcon from "./SettingsIcon";
import PlusIcon from "./PlusIcon";
import type { IconParameters } from "./types";
export { IconParameters, HomeIcon, SettingsIcon, PlusIcon };

View file

@ -0,0 +1,26 @@
interface IconParameters {
width?: number
height?: number
fill?: string
stroke?: string
strokeWidth?: number
}
const defaultFillIconParameters: IconParameters = {
width: 24,
height: 24,
fill: 'currentColor',
stroke: 'none',
strokeWidth: 0,
}
const defaultStrokeIconParameters: IconParameters = {
width: 24,
height: 24,
fill: 'none',
stroke: 'currentColor',
strokeWidth: 1.5,
}
export type { IconParameters }
export { defaultFillIconParameters, defaultStrokeIconParameters }

View file

@ -30,10 +30,15 @@ const createChannel = async (name: string, communityId: string) => {
});
};
const updateChannel = async (id: string, name?: string) => {
const updateChannel = async (
id: string,
name?: string,
description?: string,
) => {
const data = await updateChannelApi({
id: id,
name: name,
description: description,
});
dispatch({

View file

@ -1,14 +1,19 @@
import {
fetchCommunityApi,
createCommunityApi,
updateCommunityApi,
removeCommunityApi,
fetchCommunityChannelsApi,
fetchCommunityRolesApi,
fetchCommunityMembersApi,
fetchCommunityInvitesApi,
} from "../../api/community";
import { CommunityActionTypes } from "../../store/community";
import { ChannelActionTypes } from "../../store/channel";
import { RoleActionTypes } from "../../store/role";
import { UserActionTypes } from "../../store/user";
import { dispatch } from "../../store/state";
import { dispatch, state } from "../../store/state";
import { InviteActionTypes } from "../../store/invite";
const fetchCommunity = async (id: string) => {
const data = await fetchCommunityApi({
@ -21,6 +26,66 @@ const fetchCommunity = async (id: string) => {
});
};
const createCommunity = async (name: string) => {
const data = await createCommunityApi({
name: name,
});
dispatch({
type: CommunityActionTypes.CREATE_COMMUNITY_FINISH,
payload: data,
});
if (state.user.loggedUserId) {
dispatch({
type: UserActionTypes.FETCH_USER_COMMUNITIES_START,
payload: state.user.loggedUserId,
});
}
};
const updateCommunity = async (
id: string,
name?: string,
description?: string,
) => {
const data = await updateCommunityApi({
id: id,
name: name,
description: description,
});
dispatch({
type: CommunityActionTypes.UPDATE_COMMUNITY_FINISH,
payload: data,
});
if (state.user.loggedUserId) {
dispatch({
type: UserActionTypes.FETCH_USER_COMMUNITIES_START,
payload: state.user.loggedUserId,
});
}
};
const removeCommunity = async (id: string) => {
const data = await removeCommunityApi({
id: id,
});
dispatch({
type: CommunityActionTypes.REMOVE_COMMUNITY_FINISH,
payload: data,
});
if (state.user.loggedUserId) {
dispatch({
type: UserActionTypes.FETCH_USER_COMMUNITIES_START,
payload: state.user.loggedUserId,
});
}
};
const fetchCommunityChannels = async (id: string) => {
const data = await fetchCommunityChannelsApi({
id: id,
@ -75,9 +140,31 @@ const fetchCommunityMembers = async (id: string) => {
});
};
const fetchCommunityInvites = async (id: string) => {
const data = await fetchCommunityInvitesApi({
id: id,
});
dispatch({
type: CommunityActionTypes.FETCH_COMMUNITY_INVITES_FINISH,
payload: data,
});
data.invites.forEach((invite) => {
dispatch({
type: InviteActionTypes.SET_INVITE,
payload: invite,
});
});
};
export {
fetchCommunity,
createCommunity,
updateCommunity,
removeCommunity,
fetchCommunityChannels,
fetchCommunityRoles,
fetchCommunityMembers,
fetchCommunityInvites,
};

View file

@ -0,0 +1 @@
export * from "./invite";

View file

@ -0,0 +1,50 @@
import {
fetchInviteApi,
removeInviteApi,
acceptInviteApi,
} from "../../api/invite";
import { InviteActionTypes } from "../../store/invite";
import { dispatch, state } from "../../store/state";
import { UserActionTypes } from "../../store/user";
const fetchInvite = async (id: string) => {
const data = await fetchInviteApi({
id: id,
});
dispatch({
type: InviteActionTypes.FETCH_INVITE_FINISH,
payload: data,
});
};
const removeInvite = async (id: string) => {
const data = await removeInviteApi({
id: id,
});
dispatch({
type: InviteActionTypes.REMOVE_INVITE_FINISH,
payload: data,
});
};
const acceptInvite = async (id: string) => {
const data = await acceptInviteApi({
id: id,
});
dispatch({
type: InviteActionTypes.ACCEPT_INVITE_FINISH,
payload: data,
});
if (state.user.loggedUserId) {
dispatch({
type: UserActionTypes.FETCH_USER_COMMUNITIES_START,
payload: state.user.loggedUserId,
});
}
};
export { fetchInvite, removeInvite, acceptInvite };

View file

@ -0,0 +1 @@
export * from "./session";

View file

@ -0,0 +1,27 @@
import { fetchSessionApi, removeSessionApi } from "../../api/session";
import { SessionActionTypes } from "../../store/session";
import { dispatch } from "../../store/state";
const fetchSession = async (id: string) => {
const data = await fetchSessionApi({
id: id,
});
dispatch({
type: SessionActionTypes.FETCH_SESSION_FINISH,
payload: data,
});
};
const removeSession = async (id: string) => {
const data = await removeSessionApi({
id: id,
});
dispatch({
type: SessionActionTypes.REMOVE_SESSION_FINISH,
payload: data,
});
};
export { fetchSession, removeSession };

View file

@ -1,11 +1,13 @@
import {
fetchLoggedUserApi,
fetchUserApi,
fetchUserSessionsApi,
fetchUserCommunitiesApi,
} from "../../api/user";
import { UserActionTypes } from "../../store/user";
import { CommunityActionTypes } from "../../store/community";
import { dispatch } from "../../store/state";
import { SessionActionTypes } from "../../store/session";
const fetchLoggedUser = async () => {
const data = await fetchLoggedUserApi();
@ -27,6 +29,24 @@ const fetchUser = async (id: string) => {
});
};
const fetchUserSessions = async (id: string) => {
const data = await fetchUserSessionsApi({
id: id,
});
dispatch({
type: UserActionTypes.FETCH_USER_SESSIONS_FINISH,
payload: data,
});
data.sessions.forEach((session) => {
dispatch({
type: SessionActionTypes.SET_SESSION,
payload: session,
});
});
};
const fetchUserCommunities = async (id: string) => {
const data = await fetchUserCommunitiesApi({
id: id,
@ -45,4 +65,4 @@ const fetchUserCommunities = async (id: string) => {
});
};
export { fetchLoggedUser, fetchUser, fetchUserCommunities };
export { fetchLoggedUser, fetchUser, fetchUserSessions, fetchUserCommunities };

View file

@ -1,21 +1,30 @@
import { AppActionTypes, AppAction } from "./app";
import { AuthActionTypes, AuthAction } from "./auth";
import { UserActionTypes, UserAction } from "./user";
import { CommunityActionTypes, CommunityAction } from "./community";
import { ChannelActionTypes, ChannelAction } from "./channel";
import { RoleActionTypes, RoleAction } from "./role";
import { SessionActionTypes, SessionAction } from "./session";
import { InviteActionTypes, InviteAction } from "./invite";
type ActionTypes =
| AppActionTypes
| AuthActionTypes
| UserActionTypes
| CommunityActionTypes
| ChannelActionTypes
| RoleActionTypes;
| RoleActionTypes
| SessionActionTypes
| InviteActionTypes;
type Action =
| AppAction
| AuthAction
| UserAction
| CommunityAction
| ChannelAction
| RoleAction;
| RoleAction
| SessionAction
| InviteAction;
export { type Action, type ActionTypes };

12
src/store/app/actions.ts Normal file
View file

@ -0,0 +1,12 @@
enum AppActionTypes {
SET_HOME_OPEN = "SET_HOME_OPEN",
SET_SETTINGS_OPEN = "SET_SETTINGS_OPEN",
SET_ADD_COMMUNITY_OPEN = "SET_ADD_COMMUNITY_OPEN",
}
type AppAction =
| { type: AppActionTypes.SET_HOME_OPEN; payload: boolean }
| { type: AppActionTypes.SET_SETTINGS_OPEN; payload: boolean }
| { type: AppActionTypes.SET_ADD_COMMUNITY_OPEN; payload: boolean };
export { AppActionTypes, type AppAction };

21
src/store/app/app.ts Normal file
View file

@ -0,0 +1,21 @@
import { setState } from "../state";
import { AppActionTypes, AppAction } from "./actions";
import { IAppState } from "./types";
function appReducer(_state: IAppState, action: AppAction) {
switch (action.type) {
case AppActionTypes.SET_HOME_OPEN:
setState("app", "homeOpen", action.payload);
break;
case AppActionTypes.SET_SETTINGS_OPEN:
setState("app", "dialogsOpen", "settingsOpen", false);
setState("app", "dialogsOpen", "settingsOpen", action.payload);
break;
case AppActionTypes.SET_ADD_COMMUNITY_OPEN:
setState("app", "dialogsOpen", "addCommunityOpen", false);
setState("app", "dialogsOpen", "addCommunityOpen", action.payload);
break;
}
}
export { appReducer };

3
src/store/app/index.ts Normal file
View file

@ -0,0 +1,3 @@
export * from "./app";
export * from "./actions";
export * from "./types";

11
src/store/app/types.ts Normal file
View file

@ -0,0 +1,11 @@
interface IAppState {
homeOpen: boolean;
dialogsOpen: IDialogsOpen;
}
interface IDialogsOpen {
settingsOpen: boolean;
addCommunityOpen: boolean;
}
export { type IAppState, type IDialogsOpen };

View file

@ -1,5 +1,6 @@
import {
ICreateChannelRequest,
IUpdateChannelRequest,
IFetchChannelResponse,
ICreateChannelResponse,
IUpdateChannelResponse,
@ -27,7 +28,7 @@ type ChannelAction =
}
| {
type: ChannelActionTypes.SET_ACTIVE_CHANNEL;
payload: string;
payload: string | undefined;
}
| { type: ChannelActionTypes.FETCH_CHANNEL_START; payload: string }
| {
@ -42,7 +43,10 @@ type ChannelAction =
type: ChannelActionTypes.CREATE_CHANNEL_FINISH;
payload: ICreateChannelResponse;
}
| { type: ChannelActionTypes.UPDATE_CHANNEL_START; payload: string }
| {
type: ChannelActionTypes.UPDATE_CHANNEL_START;
payload: IUpdateChannelRequest;
}
| {
type: ChannelActionTypes.UPDATE_CHANNEL_FINISH;
payload: IUpdateChannelResponse;

View file

@ -26,27 +26,26 @@ function channelReducer(state: IChannelState, action: ChannelAction) {
createChannel(action.payload.name, action.payload.communityId);
break;
case ChannelActionTypes.CREATE_CHANNEL_FINISH:
setState("channel", "channels", {
...state.channels,
[action.payload.id]: action.payload,
});
setState("channel", "channels", action.payload.id, action.payload);
break;
case ChannelActionTypes.UPDATE_CHANNEL_START:
updateChannel(action.payload);
updateChannel(
action.payload.id,
action.payload.name,
action.payload.description,
);
break;
case ChannelActionTypes.UPDATE_CHANNEL_FINISH:
setState("channel", "channels", {
...state.channels,
[action.payload.id]: action.payload,
});
setState("channel", "channels", action.payload.id, action.payload);
break;
case ChannelActionTypes.REMOVE_CHANNEL_START:
removeChannel(action.payload);
break;
case ChannelActionTypes.REMOVE_CHANNEL_FINISH:
setState("channel", "channels", {
...state.channels,
[action.payload.id]: undefined,
setState("channel", "channels", (channels) => {
const copy = { ...channels };
delete copy[action.payload.id];
return copy;
});
break;
}

View file

@ -1,5 +1,5 @@
interface IChannelState {
active: string;
active?: string;
channels: Record<string, IChannel>;
}

View file

@ -1,8 +1,14 @@
import {
ICreateCommunityRequest,
IUpdateCommunityRequest,
IFetchCommunityResponse,
ICreateCommunityResponse,
IUpdateCommunityResponse,
IRemoveCommunityResponse,
IFetchCommunityChannelsResponse,
IFetchCommunityRolesResponse,
IFetchCommunityMembersResponse,
IFetchCommunityInvitesResponse,
} from "../../api/community";
import { IFetchUserCommunity } from "../../api/user";
@ -11,12 +17,20 @@ enum CommunityActionTypes {
SET_ACTIVE_COMMUNITY = "SET_ACTIVE_COMMUNITY",
FETCH_COMMUNITY_START = "FETCH_COMMUNITY_START",
FETCH_COMMUNITY_FINISH = "FETCH_COMMUNITY_FINISH",
CREATE_COMMUNITY_START = "CREATE_COMMUNITY_START",
CREATE_COMMUNITY_FINISH = "CREATE_COMMUNITY_FINISH",
UPDATE_COMMUNITY_START = "UPDATE_COMMUNITY_START",
UPDATE_COMMUNITY_FINISH = "UPDATE_COMMUNITY_FINISH",
REMOVE_COMMUNITY_START = "REMOVE_COMMUNITY_START",
REMOVE_COMMUNITY_FINISH = "REMOVE_COMMUNITY_FINISH",
FETCH_COMMUNITY_CHANNELS_START = "FETCH_COMMUNITY_CHANNELS_START",
FETCH_COMMUNITY_CHANNELS_FINISH = "FETCH_COMMUNITY_CHANNELS_FINISH",
FETCH_COMMUNITY_ROLES_START = "FETCH_COMMUNITY_ROLES_START",
FETCH_COMMUNITY_ROLES_FINISH = "FETCH_COMMUNITY_ROLES_FINISH",
FETCH_COMMUNITY_MEMBERS_START = "FETCH_COMMUNITY_MEMBERS_START",
FETCH_COMMUNITY_MEMBERS_FINISH = "FETCH_COMMUNITY_MEMBERS_FINISH",
FETCH_COMMUNITY_INVITES_START = "FETCH_COMMUNITY_INVITES_START",
FETCH_COMMUNITY_INVITES_FINISH = "FETCH_COMMUNITY_INVITES_FINISH",
}
type CommunityAction =
@ -26,13 +40,34 @@ type CommunityAction =
}
| {
type: CommunityActionTypes.SET_ACTIVE_COMMUNITY;
payload: string;
payload: string | undefined;
}
| { type: CommunityActionTypes.FETCH_COMMUNITY_START; payload: string }
| {
type: CommunityActionTypes.FETCH_COMMUNITY_FINISH;
payload: IFetchCommunityResponse;
}
| {
type: CommunityActionTypes.CREATE_COMMUNITY_START;
payload: ICreateCommunityRequest;
}
| {
type: CommunityActionTypes.CREATE_COMMUNITY_FINISH;
payload: ICreateCommunityResponse;
}
| {
type: CommunityActionTypes.UPDATE_COMMUNITY_START;
payload: IUpdateCommunityRequest;
}
| {
type: CommunityActionTypes.UPDATE_COMMUNITY_FINISH;
payload: IUpdateCommunityResponse;
}
| { type: CommunityActionTypes.REMOVE_COMMUNITY_START; payload: string }
| {
type: CommunityActionTypes.REMOVE_COMMUNITY_FINISH;
payload: IRemoveCommunityResponse;
}
| {
type: CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_START;
payload: string;
@ -56,6 +91,14 @@ type CommunityAction =
| {
type: CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_FINISH;
payload: IFetchCommunityMembersResponse;
}
| {
type: CommunityActionTypes.FETCH_COMMUNITY_INVITES_START;
payload: string;
}
| {
type: CommunityActionTypes.FETCH_COMMUNITY_INVITES_FINISH;
payload: IFetchCommunityInvitesResponse;
};
export { CommunityActionTypes, type CommunityAction };

View file

@ -1,8 +1,12 @@
import {
fetchCommunity,
createCommunity,
updateCommunity,
removeCommunity,
fetchCommunityChannels,
fetchCommunityRoles,
fetchCommunityMembers,
fetchCommunityInvites,
} from "../../services/community";
import { setState } from "../state";
import { CommunityActionTypes, CommunityAction } from "./actions";
@ -32,6 +36,42 @@ function communityReducer(state: ICommunityState, action: CommunityAction) {
action.payload,
);
break;
case CommunityActionTypes.CREATE_COMMUNITY_START:
createCommunity(action.payload.name);
break;
case CommunityActionTypes.CREATE_COMMUNITY_FINISH:
setState(
"community",
"communities",
action.payload.id,
action.payload,
);
break;
case CommunityActionTypes.UPDATE_COMMUNITY_START:
updateCommunity(
action.payload.id,
action.payload.name,
action.payload.description,
);
break;
case CommunityActionTypes.UPDATE_COMMUNITY_FINISH:
setState(
"community",
"communities",
action.payload.id,
action.payload,
);
break;
case CommunityActionTypes.REMOVE_COMMUNITY_START:
removeCommunity(action.payload);
break;
case CommunityActionTypes.REMOVE_COMMUNITY_FINISH:
setState("community", "communities", (communities) => {
const copy = { ...communities };
delete copy[action.payload.id];
return copy;
});
break;
case CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_START:
fetchCommunityChannels(action.payload);
break;
@ -56,6 +96,14 @@ function communityReducer(state: ICommunityState, action: CommunityAction) {
members: action.payload.members.map((member) => member.id),
});
break;
case CommunityActionTypes.FETCH_COMMUNITY_INVITES_START:
fetchCommunityInvites(action.payload);
break;
case CommunityActionTypes.FETCH_COMMUNITY_INVITES_FINISH:
setState("community", "communities", action.payload.id, {
invites: action.payload.invites.map((invite) => invite.id),
});
break;
}
}

View file

@ -1,5 +1,5 @@
interface ICommunityState {
active: string;
active?: string;
communities: Record<string, ICommunity>;
}
@ -12,6 +12,7 @@ interface ICommunity {
channels?: string[];
roles?: string[];
members?: string[];
invites?: string[];
}
export { type ICommunityState, type ICommunity };

View file

@ -0,0 +1,39 @@
import { IFetchCommunityInvite } from "../../api/community";
import {
IFetchInviteResponse,
IRemoveInviteResponse,
IAcceptInviteResponse,
} from "../../api/invite";
enum InviteActionTypes {
SET_INVITE = "SET_INVITE",
FETCH_INVITE_START = "FETCH_INVITE_START",
FETCH_INVITE_FINISH = "FETCH_INVITE_FINISH",
REMOVE_INVITE_START = "REMOVE_INVITE_START",
REMOVE_INVITE_FINISH = "REMOVE_INVITE_FINISH",
ACCEPT_INVITE_START = "ACCEPT_INVITE_START",
ACCEPT_INVITE_FINISH = "ACCEPT_INVITE_FINISH",
}
type InviteAction =
| {
type: InviteActionTypes.SET_INVITE;
payload: IFetchCommunityInvite;
}
| { type: InviteActionTypes.FETCH_INVITE_START; payload: string }
| {
type: InviteActionTypes.FETCH_INVITE_FINISH;
payload: IFetchInviteResponse;
}
| { type: InviteActionTypes.REMOVE_INVITE_START; payload: string }
| {
type: InviteActionTypes.REMOVE_INVITE_FINISH;
payload: IRemoveInviteResponse;
}
| { type: InviteActionTypes.ACCEPT_INVITE_START; payload: string }
| {
type: InviteActionTypes.ACCEPT_INVITE_FINISH;
payload: IAcceptInviteResponse;
};
export { InviteActionTypes, type InviteAction };

View file

@ -0,0 +1,3 @@
export * from "./invite";
export * from "./actions";
export * from "./types";

View file

@ -0,0 +1,35 @@
import { fetchInvite, removeInvite, acceptInvite } from "../../services/invite";
import { setState } from "../state";
import { InviteActionTypes, InviteAction } from "./actions";
import { IInviteState } from "./types";
function inviteReducer(state: IInviteState, action: InviteAction) {
switch (action.type) {
case InviteActionTypes.SET_INVITE:
setState("invite", "invites", action.payload.id, action.payload);
break;
case InviteActionTypes.FETCH_INVITE_START:
fetchInvite(action.payload);
break;
case InviteActionTypes.FETCH_INVITE_FINISH:
setState("invite", "invites", action.payload.id, action.payload);
break;
case InviteActionTypes.REMOVE_INVITE_START:
removeInvite(action.payload);
break;
case InviteActionTypes.REMOVE_INVITE_FINISH:
setState("invite", "invites", (invites) => {
const copy = { ...invites };
delete copy[action.payload.id];
return copy;
});
break;
case InviteActionTypes.ACCEPT_INVITE_START:
acceptInvite(action.payload);
break;
case InviteActionTypes.ACCEPT_INVITE_FINISH:
break;
}
}
export { inviteReducer };

17
src/store/invite/types.ts Normal file
View file

@ -0,0 +1,17 @@
interface IInviteState {
invites: Record<string, IInvite>;
}
interface IInvite {
id: string;
communityId?: string;
valid?: boolean;
unlimitedInvites?: boolean;
hasExpiration?: boolean;
totalInvites?: number;
remainingInvites?: number;
creationDate?: number;
expirationDate?: number;
}
export { type IInviteState, type IInvite };

View file

@ -1,17 +1,23 @@
import { IState } from "./types";
import { Action } from "./actions";
import { AppAction, appReducer } from "./app";
import { AuthAction, authReducer } from "./auth";
import { UserAction, userReducer } from "./user";
import { CommunityAction, communityReducer } from "./community";
import { ChannelAction, channelReducer } from "./channel";
import { RoleAction, roleReducer } from "./role";
import { SessionAction, sessionReducer } from "./session";
import { InviteAction, inviteReducer } from "./invite";
function reducer(state: IState, action: Action) {
appReducer(state.app, action as AppAction);
authReducer(state.auth, action as AuthAction);
userReducer(state.user, action as UserAction);
communityReducer(state.community, action as CommunityAction);
channelReducer(state.channel, action as ChannelAction);
roleReducer(state.role, action as RoleAction);
sessionReducer(state.session, action as SessionAction);
inviteReducer(state.invite, action as InviteAction);
}
export { reducer };

View file

@ -23,27 +23,22 @@ function roleReducer(state: IRoleState, action: RoleAction) {
createRole(action.payload.name, action.payload.communityId);
break;
case RoleActionTypes.CREATE_ROLE_FINISH:
setState("role", "roles", {
...state.roles,
[action.payload.id]: action.payload,
});
setState("role", "roles", action.payload.id, action.payload);
break;
case RoleActionTypes.UPDATE_ROLE_START:
updateRole(action.payload);
break;
case RoleActionTypes.UPDATE_ROLE_FINISH:
setState("role", "roles", {
...state.roles,
[action.payload.id]: action.payload,
});
setState("role", "roles", action.payload.id, action.payload);
break;
case RoleActionTypes.REMOVE_ROLE_START:
removeRole(action.payload);
break;
case RoleActionTypes.REMOVE_ROLE_FINISH:
setState("role", "roles", {
...state.roles,
[action.payload.id]: undefined,
setState("role", "roles", (roles) => {
const copy = { ...roles };
delete copy[action.payload.id];
return copy;
});
break;
}

View file

@ -0,0 +1,31 @@
import { IFetchUserSession } from "../../api/user";
import {
IFetchSessionResponse,
IRemoveSessionResponse,
} from "../../api/session";
enum SessionActionTypes {
SET_SESSION = "SET_SESSION",
FETCH_SESSION_START = "FETCH_SESSION_START",
FETCH_SESSION_FINISH = "FETCH_SESSION_FINISH",
REMOVE_SESSION_START = "REMOVE_SESSION_START",
REMOVE_SESSION_FINISH = "REMOVE_SESSION_FINISH",
}
type SessionAction =
| {
type: SessionActionTypes.SET_SESSION;
payload: IFetchUserSession;
}
| { type: SessionActionTypes.FETCH_SESSION_START; payload: string }
| {
type: SessionActionTypes.FETCH_SESSION_FINISH;
payload: IFetchSessionResponse;
}
| { type: SessionActionTypes.REMOVE_SESSION_START; payload: string }
| {
type: SessionActionTypes.REMOVE_SESSION_FINISH;
payload: IRemoveSessionResponse;
};
export { SessionActionTypes, type SessionAction };

View file

@ -0,0 +1,3 @@
export * from "./session";
export * from "./actions";
export * from "./types";

View file

@ -0,0 +1,30 @@
import { fetchSession, removeSession } from "../../services/session";
import { setState } from "../state";
import { SessionActionTypes, SessionAction } from "./actions";
import { ISessionState } from "./types";
function sessionReducer(state: ISessionState, action: SessionAction) {
switch (action.type) {
case SessionActionTypes.SET_SESSION:
setState("session", "sessions", action.payload.id, action.payload);
break;
case SessionActionTypes.FETCH_SESSION_START:
fetchSession(action.payload);
break;
case SessionActionTypes.FETCH_SESSION_FINISH:
setState("session", "sessions", action.payload.id, action.payload);
break;
case SessionActionTypes.REMOVE_SESSION_START:
removeSession(action.payload);
break;
case SessionActionTypes.REMOVE_SESSION_FINISH:
setState("session", "sessions", (sessions) => {
const copy = { ...sessions };
delete copy[action.payload.id];
return copy;
});
break;
}
}
export { sessionReducer };

View file

@ -0,0 +1,11 @@
interface ISessionState {
sessions: Record<string, ISession>;
}
interface ISession {
id: string;
userId?: string;
creationDate?: number;
}
export { type ISessionState, type ISession };

View file

@ -4,6 +4,13 @@ import { Action } from "./actions";
import { reducer } from "./reducers";
const [state, setState] = createStore<IState>({
app: {
homeOpen: true,
dialogsOpen: {
settingsOpen: false,
addCommunityOpen: false,
},
},
auth: {
session: undefined,
},
@ -20,6 +27,12 @@ const [state, setState] = createStore<IState>({
role: {
roles: {},
},
session: {
sessions: {},
},
invite: {
invites: {},
},
});
function dispatch(action: Action) {

View file

@ -1,15 +1,21 @@
import { IAppState } from "./app";
import { IAuthState } from "./auth";
import { IUserState } from "./user";
import { ICommunityState } from "./community";
import { IChannelState } from "./channel";
import { IRoleState } from "./role";
import { ISessionState } from "./session";
import { IInviteState } from "./invite";
interface IState {
app: IAppState;
auth: IAuthState;
user: IUserState;
community: ICommunityState;
channel: IChannelState;
role: IRoleState;
session: ISessionState;
invite: IInviteState;
}
export { type IState };

View file

@ -2,6 +2,7 @@ import { IFetchCommunityMember } from "../../api/community";
import {
IFetchLoggedUserResponse,
IFetchUserResponse,
IFetchUserSessionsResponse,
IFetchUserCommunitiesResponse,
} from "../../api/user";
@ -11,6 +12,8 @@ enum UserActionTypes {
FETCH_LOGGED_USER_ID_FINISH = "FETCH_LOGGED_USER_ID_FINISH",
FETCH_USER_START = "FETCH_USER_START",
FETCH_USER_FINISH = "FETCH_USER_FINISH",
FETCH_USER_SESSIONS_START = "FETCH_USER_SESSIONS_START",
FETCH_USER_SESSIONS_FINISH = "FETCH_USER_SESSIONS_FINISH",
FETCH_USER_COMMUNITIES_START = "FETCH_USER_COMMUNITIES_START",
FETCH_USER_COMMUNITIES_FINISH = "FETCH_USER_COMMUNITIES_FINISH",
}
@ -27,6 +30,11 @@ type UserAction =
}
| { type: UserActionTypes.FETCH_USER_START; payload: string }
| { type: UserActionTypes.FETCH_USER_FINISH; payload: IFetchUserResponse }
| { type: UserActionTypes.FETCH_USER_SESSIONS_START; payload: string }
| {
type: UserActionTypes.FETCH_USER_SESSIONS_FINISH;
payload: IFetchUserSessionsResponse;
}
| { type: UserActionTypes.FETCH_USER_COMMUNITIES_START; payload: string }
| {
type: UserActionTypes.FETCH_USER_COMMUNITIES_FINISH;

View file

@ -11,6 +11,7 @@ interface IUser {
admin?: boolean;
registerDate?: number;
lastLogin?: number;
sessions?: string[];
communities?: string[];
}

View file

@ -1,6 +1,7 @@
import {
fetchLoggedUser,
fetchUser,
fetchUserSessions,
fetchUserCommunities,
} from "../../services/user";
import { setState } from "../state";
@ -24,6 +25,14 @@ function userReducer(state: IUserState, action: UserAction) {
case UserActionTypes.FETCH_USER_FINISH:
setState("user", "users", action.payload.id, action.payload);
break;
case UserActionTypes.FETCH_USER_SESSIONS_START:
fetchUserSessions(action.payload);
break;
case UserActionTypes.FETCH_USER_SESSIONS_FINISH:
setState("user", "users", action.payload.id, {
sessions: action.payload.sessions.map((session) => session.id),
});
break;
case UserActionTypes.FETCH_USER_COMMUNITIES_START:
fetchUserCommunities(action.payload);
break;

View file

@ -1,5 +1,4 @@
import { createEffect, createMemo, type Component } from "solid-js";
import { CommunityView } from "../CommunityView";
import { dispatch, state } from "../../store/state";
import { CommunityActionTypes, ICommunity } from "../../store/community";
import { ChannelActionTypes } from "../../store/channel";
@ -23,7 +22,7 @@ const ChannelView: Component = () => {
const communityInfo = createMemo<ICommunity | undefined>(() => {
const activeCommunityId = state.community.active;
return state.community.communities[activeCommunityId];
return state.community.communities[activeCommunityId ?? 0];
});
createEffect(() => {
@ -63,9 +62,7 @@ const ChannelView: Component = () => {
};
return (
<div class="flex flex-row bg-stone-900 w-80 shadow-panel z-20">
<CommunityView />
<div class="h-full w-full relative">
<div class="bg-stone-900 w-80 shadow-panel z-20 h-full relative">
<CommunityBar
id={communityInfo()?.id}
name={communityInfo()?.name}
@ -78,7 +75,6 @@ const ChannelView: Component = () => {
{channelIds().map(mapChannel)}
</ul>
</div>
</div>
);
};

View file

@ -180,7 +180,7 @@ const ChatView: Component = () => {
const channelInfo = createMemo<IChannel | undefined>(() => {
const activeChannelId = state.channel.active;
return state.channel.channels[activeChannelId];
return state.channel.channels[activeChannelId ?? 0];
});
let scrollRef: HTMLUListElement | undefined;

View file

@ -2,6 +2,9 @@ import { createMemo, type Component } from "solid-js";
import { Community } from "../../components/Community";
import { dispatch, state } from "../../store/state";
import { CommunityActionTypes } from "../../store/community";
import { SidebarItem } from "../../components/SidebarItem";
import { AppActionTypes } from "../../store/app";
import { HomeIcon, PlusIcon, SettingsIcon } from "../../components/icons";
const CommunityView: Component = () => {
const communityIds = createMemo(() => {
@ -18,7 +21,25 @@ const CommunityView: Component = () => {
return loggedUser.communities ?? [];
});
const onHomeClick = () => {
dispatch({
type: CommunityActionTypes.SET_ACTIVE_COMMUNITY,
payload: undefined,
});
dispatch({
type: AppActionTypes.SET_HOME_OPEN,
payload: true,
});
};
const onCommunityClick = (id: string) => {
if (state.app.homeOpen) {
dispatch({
type: AppActionTypes.SET_HOME_OPEN,
payload: false,
});
}
dispatch({
type: CommunityActionTypes.FETCH_COMMUNITY_START,
payload: id,
@ -29,6 +50,20 @@ const CommunityView: Component = () => {
});
};
const onNewClick = () => {
dispatch({
type: AppActionTypes.SET_ADD_COMMUNITY_OPEN,
payload: true,
});
};
const onSettingsClick = () => {
dispatch({
type: AppActionTypes.SET_SETTINGS_OPEN,
payload: true,
});
};
const mapCommunity = (communityId: string) => {
const community = state.community.communities[communityId];
if (!community) {
@ -49,8 +84,22 @@ const CommunityView: Component = () => {
};
return (
<div class="bg-stone-950 w-18 h-full shadow-panel z-30 flex flex-col p-2 gap-2">
<div class="flex flex-col bg-stone-950 w-16 h-full shadow-panel z-30 p-2 gap-2">
<SidebarItem
icon={HomeIcon}
active={state.app.homeOpen}
onClick={onHomeClick}
/>
<div class="divider my-0"></div>
{communityIds().map(mapCommunity)}
<div class="divider my-0"></div>
<SidebarItem icon={PlusIcon} active={false} onClick={onNewClick} />
<div class="flex-1"></div>
<SidebarItem
icon={SettingsIcon}
active={false}
onClick={onSettingsClick}
/>
</div>
);
};

View file

@ -0,0 +1,42 @@
import { type Component } from "solid-js";
import { PlusIcon, SettingsIcon } from "../../components/icons";
import { HomeCard } from "../../components/HomeCard";
import { dispatch } from "../../store/state";
import { AppActionTypes } from "../../store/app";
const HomeView: Component = () => {
const onNewClick = () => {
dispatch({
type: AppActionTypes.SET_ADD_COMMUNITY_OPEN,
payload: true,
});
};
const onSettingsClick = () => {
dispatch({
type: AppActionTypes.SET_SETTINGS_OPEN,
payload: true,
});
};
return (
<div class="flex-1 flex flex-row bg-stone-900 shadow-panel z-0">
<div class="flex-1 flex flex-row items-center justify-center gap-8">
<HomeCard
title="Find a Community"
description="Find or create a new Community to chat"
icon={PlusIcon}
onClick={onNewClick}
/>
<HomeCard
title="Open Settings"
description="Adjust your settings"
icon={SettingsIcon}
onClick={onSettingsClick}
/>
</div>
</div>
);
};
export { HomeView };

View file

@ -0,0 +1 @@
export * from "./HomeView";

View file

@ -1,10 +1,13 @@
import { createEffect, createMemo, onMount, type Component } from "solid-js";
import { createEffect, onMount, type Component } from "solid-js";
import { ChannelView } from "../ChannelView";
import { ChatView } from "../ChatView";
import { MemberView } from "../MemberView";
import { dispatch, state } from "../../store/state";
import { UserActionTypes } from "../../store/user";
import { AuthActionTypes } from "../../store/auth";
import { HomeView } from "../HomeView";
import { CommunityView } from "../CommunityView";
import { ModalView } from "../ModalView";
const MainView: Component = () => {
onMount(() => {
@ -30,9 +33,19 @@ const MainView: Component = () => {
return (
<div class="flex flex-row h-screen">
<ModalView />
<CommunityView />
{state.app.homeOpen ? (
<>
<HomeView />
</>
) : (
<>
<ChannelView />
<ChatView />
<MemberView />
</>
)}
</div>
);
};

View file

@ -0,0 +1,55 @@
import { createEffect, type Component } from "solid-js";
import { dispatch, state } from "../../store/state";
import SettingsModal from "../../components/SettingsModal/SettingsModal";
import CommunityModal from "../../components/CommunityModal/CommunityModal";
import { AppActionTypes } from "../../store/app";
const ModalView: Component = () => {
let settingsModal: HTMLDialogElement;
let communityModal: HTMLDialogElement;
createEffect(() => {
if (state.app.dialogsOpen.settingsOpen) {
settingsModal.showModal();
} else {
settingsModal.close();
}
});
createEffect(() => {
if (state.app.dialogsOpen.addCommunityOpen) {
communityModal.showModal();
} else {
communityModal.close();
}
});
const onCloseSettings = () => {
dispatch({
type: AppActionTypes.SET_SETTINGS_OPEN,
payload: false,
});
};
const onCloseCommunity = () => {
dispatch({
type: AppActionTypes.SET_ADD_COMMUNITY_OPEN,
payload: false,
});
};
return (
<>
<SettingsModal
dialogRef={(element) => (settingsModal = element)}
onClose={onCloseSettings}
/>
<CommunityModal
dialogRef={(element) => (communityModal = element)}
onClose={onCloseCommunity}
/>
</>
);
};
export { ModalView };

View file

@ -0,0 +1 @@
export * from "./ModalView";