Communities and channels

This commit is contained in:
Aslan 2026-01-07 21:01:01 -05:00
parent 79dbeb6b7a
commit 280158470a
34 changed files with 558 additions and 62 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "pulsar-web", "name": "pulsar-web",
"version": "0.1.0", "version": "0.2.0",
"description": "", "description": "",
"type": "module", "type": "module",
"scripts": { "scripts": {

View file

@ -1,5 +1,14 @@
import { callApi, HTTP } from "../tools"; import { callApi, HTTP } from "../tools";
import { IFetchCommunityRequest, IFetchCommunityResponse } from "./types"; import {
IFetchCommunityRequest,
IFetchCommunityResponse,
IFetchCommunityChannelsRequest,
IFetchCommunityChannelsResponse,
IFetchCommunityRolesRequest,
IFetchCommunityRolesResponse,
IFetchCommunityMembersRequest,
IFetchCommunityMembersResponse,
} from "./types";
const fetchCommunityApi = async ( const fetchCommunityApi = async (
request: IFetchCommunityRequest, request: IFetchCommunityRequest,
@ -7,4 +16,27 @@ const fetchCommunityApi = async (
return await callApi(HTTP.GET, `community/${request.id}`); return await callApi(HTTP.GET, `community/${request.id}`);
}; };
export { fetchCommunityApi }; const fetchCommunityChannelsApi = async (
request: IFetchCommunityChannelsRequest,
): Promise<IFetchCommunityChannelsResponse> => {
return await callApi(HTTP.GET, `community/${request.id}/channels`);
};
const fetchCommunityRolesApi = async (
request: IFetchCommunityRolesRequest,
): Promise<IFetchCommunityRolesResponse> => {
return await callApi(HTTP.GET, `community/${request.id}/roles`);
};
const fetchCommunityMembersApi = async (
request: IFetchCommunityMembersRequest,
): Promise<IFetchCommunityMembersResponse> => {
return await callApi(HTTP.GET, `community/${request.id}/members`);
};
export {
fetchCommunityApi,
fetchCommunityChannelsApi,
fetchCommunityRolesApi,
fetchCommunityMembersApi,
};

View file

@ -12,8 +12,59 @@ interface IFetchCommunityRequest {
interface IFetchCommunityResponse extends IFetchCommunity {} interface IFetchCommunityResponse extends IFetchCommunity {}
interface IFetchCommunityChannelsRequest {
id: string;
}
interface IFetchCommunityChannelsResponse {
id: string;
channels: IFetchCommunityChannel[];
}
interface IFetchCommunityChannel {
id: string;
name: string;
}
interface IFetchCommunityRolesRequest {
id: string;
}
interface IFetchCommunityRolesResponse {
id: string;
roles: IFetchCommunityRole[];
}
interface IFetchCommunityRole {
id: string;
name: string;
}
interface IFetchCommunityMembersRequest {
id: string;
}
interface IFetchCommunityMembersResponse {
id: string;
members: IFetchCommunityMember[];
}
interface IFetchCommunityMember {
id: string;
username: string;
}
export { export {
type IFetchCommunity, type IFetchCommunity,
type IFetchCommunityRequest, type IFetchCommunityRequest,
type IFetchCommunityResponse, type IFetchCommunityResponse,
type IFetchCommunityChannelsRequest,
type IFetchCommunityChannelsResponse,
type IFetchCommunityChannel,
type IFetchCommunityRolesRequest,
type IFetchCommunityRolesResponse,
type IFetchCommunityRole,
type IFetchCommunityMembersRequest,
type IFetchCommunityMembersResponse,
type IFetchCommunityMember,
}; };

View file

@ -1,7 +1,16 @@
import type { Component } from "solid-js"; import type { Component } from "solid-js";
import { IChannelProps } from "./types";
const Channel: Component = () => { const Channel: Component<IChannelProps> = (props: IChannelProps) => {
return <div></div>; 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)}
>
<div class="font-bold text-xl">&nbsp#</div>
<div class="font-bold">{props.name}</div>
</li>
);
}; };
export { Channel }; export { Channel };

View file

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

View file

@ -0,0 +1,8 @@
interface IChannelProps {
id: string;
name: string;
active: boolean;
onChannelClick: (id: string) => void;
}
export { type IChannelProps };

View file

@ -1,9 +1,15 @@
import type { Component } from "solid-js"; import type { Component } from "solid-js";
import { IChannelBarProps } from "./types";
const ChannelBar: Component = () => { const ChannelBar: Component<IChannelBarProps> = (props: IChannelBarProps) => {
return ( return (
<div class="absolute w-full top-0 z-10"> <div class="absolute w-full top-0 z-10">
<div class="bg-stone-800/25 backdrop-blur-md h-16 w-full shadow-bar p-2"></div> <div class="flex flex-col justify-center bg-stone-800/25 backdrop-blur-md h-16 w-full shadow-bar px-5">
<h2 class="text-sm font-bold">
{props.name ? `# ${props.name}` : undefined}
</h2>
<p class="text-xs">{props.description}</p>
</div>
</div> </div>
); );
}; };

View file

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

View file

@ -0,0 +1,7 @@
interface IChannelBarProps {
id?: string;
name?: string;
description?: string;
}
export { type IChannelBarProps };

View file

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

View file

@ -2,6 +2,7 @@ interface ICommunityProps {
id: string; id: string;
name: string; name: string;
avatar: string; avatar: string;
active: boolean;
onCommunityClick: (id: string) => void; onCommunityClick: (id: string) => void;
} }

View file

@ -0,0 +1,19 @@
import type { Component } from "solid-js";
import { ICommunityBarProps } from "./types";
const CommunityBar: Component<ICommunityBarProps> = (
props: ICommunityBarProps,
) => {
return (
<div
class={`absolute w-full top-0 z-10 bg-cover bg-top bg-no-repeat bg-[url('${props.avatar}')]`}
>
<div class="flex flex-col justify-center bg-stone-800/25 backdrop-blur-md h-16 w-full shadow-bar px-5">
<h2 class="text-sm font-bold">{props.name}</h2>
<p class="text-xs">{props.description}</p>
</div>
</div>
);
};
export { CommunityBar };

View file

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

View file

@ -0,0 +1,8 @@
interface ICommunityBarProps {
id?: string;
name?: string;
description?: string;
avatar?: string;
}
export { type ICommunityBarProps };

View file

@ -1,7 +1,20 @@
import type { Component } from "solid-js"; import type { Component } from "solid-js";
import { IMemberProps } from "./types";
const Member: Component = () => { const Member: Component<IMemberProps> = (props: IMemberProps) => {
return <div></div>; 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)}
>
<div class="avatar">
<div class="w-9 rounded-full">
<img src={props.avatar} />
</div>
</div>
<div class="font-bold">{props.username}</div>
</li>
);
}; };
export { Member }; export { Member };

View file

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

View file

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

View file

@ -3,7 +3,7 @@ import { IMessageProps } from "./types";
const Message: Component<IMessageProps> = (props: IMessageProps) => { const Message: Component<IMessageProps> = (props: IMessageProps) => {
return ( return (
<li class="list-row hover:bg-stone-700"> <li class="list-row p-3 hover:bg-stone-700">
<div <div
class="avatar cursor-pointer" class="avatar cursor-pointer"
onClick={() => props.onProfileClick(props.userId)} onClick={() => props.onProfileClick(props.userId)}

View file

@ -1,4 +1,5 @@
import type { Component } from "solid-js"; import type { Component } from "solid-js";
import { state } from "../../store/state";
const MessageBar: Component = () => { const MessageBar: Component = () => {
return ( return (

View file

@ -1,5 +1,13 @@
import { fetchCommunityApi } from "../../api/community"; import {
fetchCommunityApi,
fetchCommunityChannelsApi,
fetchCommunityRolesApi,
fetchCommunityMembersApi,
} from "../../api/community";
import { CommunityActionTypes } from "../../store/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 } from "../../store/state";
const fetchCommunity = async (id: string) => { const fetchCommunity = async (id: string) => {
@ -13,4 +21,63 @@ const fetchCommunity = async (id: string) => {
}); });
}; };
export { fetchCommunity }; const fetchCommunityChannels = async (id: string) => {
const data = await fetchCommunityChannelsApi({
id: id,
});
dispatch({
type: CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_FINISH,
payload: data,
});
data.channels.forEach((channel) => {
dispatch({
type: ChannelActionTypes.SET_CHANNEL,
payload: channel,
});
});
};
const fetchCommunityRoles = async (id: string) => {
const data = await fetchCommunityRolesApi({
id: id,
});
dispatch({
type: CommunityActionTypes.FETCH_COMMUNITY_ROLES_FINISH,
payload: data,
});
data.roles.forEach((role) => {
dispatch({
type: RoleActionTypes.SET_ROLE,
payload: role,
});
});
};
const fetchCommunityMembers = async (id: string) => {
const data = await fetchCommunityMembersApi({
id: id,
});
dispatch({
type: CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_FINISH,
payload: data,
});
data.members.forEach((member) => {
dispatch({
type: UserActionTypes.SET_USER,
payload: member,
});
});
};
export {
fetchCommunity,
fetchCommunityChannels,
fetchCommunityRoles,
fetchCommunityMembers,
};

View file

@ -5,10 +5,13 @@ import {
IUpdateChannelResponse, IUpdateChannelResponse,
IRemoveChannelResponse, IRemoveChannelResponse,
} from "../../api/channel"; } from "../../api/channel";
import { IFetchCommunityChannel } from "../../api/community";
enum ChannelActionTypes { enum ChannelActionTypes {
FETCH_CHANNEL_START = "FETCH_COMMUNITY_START", SET_CHANNEL = "SET_CHANNEL",
FETCH_CHANNEL_FINISH = "FETCH_COMMUNITY_FINISH", SET_ACTIVE_CHANNEL = "SET_ACTIVE_CHANNEL",
FETCH_CHANNEL_START = "FETCH_CHANNEL_START",
FETCH_CHANNEL_FINISH = "FETCH_CHANNEL_FINISH",
CREATE_CHANNEL_START = "CREATE_CHANNEL_START", CREATE_CHANNEL_START = "CREATE_CHANNEL_START",
CREATE_CHANNEL_FINISH = "CREATE_CHANNEL_FINISH", CREATE_CHANNEL_FINISH = "CREATE_CHANNEL_FINISH",
UPDATE_CHANNEL_START = "UPDATE_CHANNEL_START", UPDATE_CHANNEL_START = "UPDATE_CHANNEL_START",
@ -18,6 +21,14 @@ enum ChannelActionTypes {
} }
type ChannelAction = type ChannelAction =
| {
type: ChannelActionTypes.SET_CHANNEL;
payload: IFetchCommunityChannel;
}
| {
type: ChannelActionTypes.SET_ACTIVE_CHANNEL;
payload: string;
}
| { type: ChannelActionTypes.FETCH_CHANNEL_START; payload: string } | { type: ChannelActionTypes.FETCH_CHANNEL_START; payload: string }
| { | {
type: ChannelActionTypes.FETCH_CHANNEL_FINISH; type: ChannelActionTypes.FETCH_CHANNEL_FINISH;

View file

@ -10,14 +10,17 @@ import { IChannelState } from "./types";
function channelReducer(state: IChannelState, action: ChannelAction) { function channelReducer(state: IChannelState, action: ChannelAction) {
switch (action.type) { switch (action.type) {
case ChannelActionTypes.SET_CHANNEL:
setState("channel", "channels", action.payload.id, action.payload);
break;
case ChannelActionTypes.SET_ACTIVE_CHANNEL:
setState("channel", "active", action.payload);
break;
case ChannelActionTypes.FETCH_CHANNEL_START: case ChannelActionTypes.FETCH_CHANNEL_START:
fetchChannel(action.payload); fetchChannel(action.payload);
break; break;
case ChannelActionTypes.FETCH_CHANNEL_FINISH: case ChannelActionTypes.FETCH_CHANNEL_FINISH:
setState("channel", "channels", { setState("channel", "channels", action.payload.id, action.payload);
...state.channels,
[action.payload.id]: action.payload,
});
break; break;
case ChannelActionTypes.CREATE_CHANNEL_START: case ChannelActionTypes.CREATE_CHANNEL_START:
createChannel(action.payload.name, action.payload.communityId); createChannel(action.payload.name, action.payload.communityId);

View file

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

View file

@ -1,21 +1,61 @@
import { IFetchCommunityResponse } from "../../api/community"; import {
IFetchCommunityResponse,
IFetchCommunityChannelsResponse,
IFetchCommunityRolesResponse,
IFetchCommunityMembersResponse,
} from "../../api/community";
import { IFetchUserCommunity } from "../../api/user"; import { IFetchUserCommunity } from "../../api/user";
enum CommunityActionTypes { enum CommunityActionTypes {
SET_COMMUNITY = "SET_COMMUNITY",
SET_ACTIVE_COMMUNITY = "SET_ACTIVE_COMMUNITY",
FETCH_COMMUNITY_START = "FETCH_COMMUNITY_START", FETCH_COMMUNITY_START = "FETCH_COMMUNITY_START",
FETCH_COMMUNITY_FINISH = "FETCH_COMMUNITY_FINISH", FETCH_COMMUNITY_FINISH = "FETCH_COMMUNITY_FINISH",
SET_COMMUNITY = "SET_COMMUNITY", 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",
} }
type CommunityAction = type CommunityAction =
| {
type: CommunityActionTypes.SET_COMMUNITY;
payload: IFetchUserCommunity;
}
| {
type: CommunityActionTypes.SET_ACTIVE_COMMUNITY;
payload: string;
}
| { type: CommunityActionTypes.FETCH_COMMUNITY_START; payload: string } | { type: CommunityActionTypes.FETCH_COMMUNITY_START; payload: string }
| { | {
type: CommunityActionTypes.FETCH_COMMUNITY_FINISH; type: CommunityActionTypes.FETCH_COMMUNITY_FINISH;
payload: IFetchCommunityResponse; payload: IFetchCommunityResponse;
} }
| { | {
type: CommunityActionTypes.SET_COMMUNITY; type: CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_START;
payload: IFetchUserCommunity; payload: string;
}
| {
type: CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_FINISH;
payload: IFetchCommunityChannelsResponse;
}
| {
type: CommunityActionTypes.FETCH_COMMUNITY_ROLES_START;
payload: string;
}
| {
type: CommunityActionTypes.FETCH_COMMUNITY_ROLES_FINISH;
payload: IFetchCommunityRolesResponse;
}
| {
type: CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_START;
payload: string;
}
| {
type: CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_FINISH;
payload: IFetchCommunityMembersResponse;
}; };
export { CommunityActionTypes, type CommunityAction }; export { CommunityActionTypes, type CommunityAction };

View file

@ -1,23 +1,59 @@
import { fetchCommunity } from "../../services/community"; import {
fetchCommunity,
fetchCommunityChannels,
fetchCommunityRoles,
fetchCommunityMembers,
} from "../../services/community";
import { setState } from "../state"; import { setState } from "../state";
import { CommunityActionTypes, CommunityAction } from "./actions"; import { CommunityActionTypes, CommunityAction } from "./actions";
import { ICommunityState } from "./types"; import { ICommunityState } from "./types";
function communityReducer(state: ICommunityState, action: CommunityAction) { function communityReducer(state: ICommunityState, action: CommunityAction) {
switch (action.type) { switch (action.type) {
case CommunityActionTypes.SET_COMMUNITY:
setState(
"community",
"communities",
action.payload.id,
action.payload,
);
break;
case CommunityActionTypes.SET_ACTIVE_COMMUNITY:
setState("community", "active", action.payload);
break;
case CommunityActionTypes.FETCH_COMMUNITY_START: case CommunityActionTypes.FETCH_COMMUNITY_START:
fetchCommunity(action.payload); fetchCommunity(action.payload);
break; break;
case CommunityActionTypes.FETCH_COMMUNITY_FINISH: case CommunityActionTypes.FETCH_COMMUNITY_FINISH:
setState("community", "communities", { setState(
...state.communities, "community",
[action.payload.id]: action.payload, "communities",
action.payload.id,
action.payload,
);
break;
case CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_START:
fetchCommunityChannels(action.payload);
break;
case CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_FINISH:
setState("community", "communities", action.payload.id, {
channels: action.payload.channels.map((channel) => channel.id),
}); });
break; break;
case CommunityActionTypes.SET_COMMUNITY: case CommunityActionTypes.FETCH_COMMUNITY_ROLES_START:
setState("community", "communities", { fetchCommunityRoles(action.payload);
...state.communities, break;
[action.payload.id]: action.payload, case CommunityActionTypes.FETCH_COMMUNITY_ROLES_FINISH:
setState("community", "communities", action.payload.id, {
roles: action.payload.roles.map((role) => role.id),
});
break;
case CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_START:
fetchCommunityMembers(action.payload);
break;
case CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_FINISH:
setState("community", "communities", action.payload.id, {
members: action.payload.members.map((member) => member.id),
}); });
break; break;
} }

View file

@ -1,4 +1,5 @@
interface ICommunityState { interface ICommunityState {
active: string;
communities: Record<string, ICommunity>; communities: Record<string, ICommunity>;
} }
@ -8,6 +9,9 @@ interface ICommunity {
description?: string; description?: string;
owner?: string; owner?: string;
creationDate?: number; creationDate?: number;
channels?: string[];
roles?: string[];
members?: string[];
} }
export { type ICommunityState, type ICommunity }; export { type ICommunityState, type ICommunity };

View file

@ -1,3 +1,4 @@
import { IFetchCommunityRole } from "../../api/community";
import { import {
ICreateRoleRequest, ICreateRoleRequest,
IFetchRoleResponse, IFetchRoleResponse,
@ -7,6 +8,7 @@ import {
} from "../../api/role"; } from "../../api/role";
enum RoleActionTypes { enum RoleActionTypes {
SET_ROLE = "SET_ROLE",
FETCH_ROLE_START = "FETCH_ROLE_START", FETCH_ROLE_START = "FETCH_ROLE_START",
FETCH_ROLE_FINISH = "FETCH_ROLE_FINISH", FETCH_ROLE_FINISH = "FETCH_ROLE_FINISH",
CREATE_ROLE_START = "CREATE_ROLE_START", CREATE_ROLE_START = "CREATE_ROLE_START",
@ -18,6 +20,10 @@ enum RoleActionTypes {
} }
type RoleAction = type RoleAction =
| {
type: RoleActionTypes.SET_ROLE;
payload: IFetchCommunityRole;
}
| { type: RoleActionTypes.FETCH_ROLE_START; payload: string } | { type: RoleActionTypes.FETCH_ROLE_START; payload: string }
| { | {
type: RoleActionTypes.FETCH_ROLE_FINISH; type: RoleActionTypes.FETCH_ROLE_FINISH;

View file

@ -10,14 +10,14 @@ import { IRoleState } from "./types";
function roleReducer(state: IRoleState, action: RoleAction) { function roleReducer(state: IRoleState, action: RoleAction) {
switch (action.type) { switch (action.type) {
case RoleActionTypes.SET_ROLE:
setState("role", "roles", action.payload.id, action.payload);
break;
case RoleActionTypes.FETCH_ROLE_START: case RoleActionTypes.FETCH_ROLE_START:
fetchRole(action.payload); fetchRole(action.payload);
break; break;
case RoleActionTypes.FETCH_ROLE_FINISH: case RoleActionTypes.FETCH_ROLE_FINISH:
setState("role", "roles", { setState("role", "roles", action.payload.id, action.payload);
...state.roles,
[action.payload.id]: action.payload,
});
break; break;
case RoleActionTypes.CREATE_ROLE_START: case RoleActionTypes.CREATE_ROLE_START:
createRole(action.payload.name, action.payload.communityId); createRole(action.payload.name, action.payload.communityId);

View file

@ -1,3 +1,4 @@
import { IFetchCommunityMember } from "../../api/community";
import { import {
IFetchLoggedUserResponse, IFetchLoggedUserResponse,
IFetchUserResponse, IFetchUserResponse,
@ -5,6 +6,7 @@ import {
} from "../../api/user"; } from "../../api/user";
enum UserActionTypes { enum UserActionTypes {
SET_USER = "SET_USER",
FETCH_LOGGED_USER_ID_START = "FETCH_LOGGED_USER_ID_START", FETCH_LOGGED_USER_ID_START = "FETCH_LOGGED_USER_ID_START",
FETCH_LOGGED_USER_ID_FINISH = "FETCH_LOGGED_USER_ID_FINISH", FETCH_LOGGED_USER_ID_FINISH = "FETCH_LOGGED_USER_ID_FINISH",
FETCH_USER_START = "FETCH_USER_START", FETCH_USER_START = "FETCH_USER_START",
@ -14,6 +16,10 @@ enum UserActionTypes {
} }
type UserAction = type UserAction =
| {
type: UserActionTypes.SET_USER;
payload: IFetchCommunityMember;
}
| { type: UserActionTypes.FETCH_LOGGED_USER_ID_START } | { type: UserActionTypes.FETCH_LOGGED_USER_ID_START }
| { | {
type: UserActionTypes.FETCH_LOGGED_USER_ID_FINISH; type: UserActionTypes.FETCH_LOGGED_USER_ID_FINISH;

View file

@ -9,6 +9,9 @@ import { IUserState } from "./types";
function userReducer(state: IUserState, action: UserAction) { function userReducer(state: IUserState, action: UserAction) {
switch (action.type) { switch (action.type) {
case UserActionTypes.SET_USER:
setState("user", "users", action.payload.id, action.payload);
break;
case UserActionTypes.FETCH_LOGGED_USER_ID_START: case UserActionTypes.FETCH_LOGGED_USER_ID_START:
fetchLoggedUser(); fetchLoggedUser();
break; break;
@ -19,23 +22,16 @@ function userReducer(state: IUserState, action: UserAction) {
fetchUser(action.payload); fetchUser(action.payload);
break; break;
case UserActionTypes.FETCH_USER_FINISH: case UserActionTypes.FETCH_USER_FINISH:
setState("user", "users", { setState("user", "users", action.payload.id, action.payload);
...state.users,
[action.payload.id]: action.payload,
});
break; break;
case UserActionTypes.FETCH_USER_COMMUNITIES_START: case UserActionTypes.FETCH_USER_COMMUNITIES_START:
fetchUserCommunities(action.payload); fetchUserCommunities(action.payload);
break; break;
case UserActionTypes.FETCH_USER_COMMUNITIES_FINISH: case UserActionTypes.FETCH_USER_COMMUNITIES_FINISH:
setState("user", "users", { setState("user", "users", action.payload.id, {
...state.users,
[action.payload.id]: {
id: action.payload.id,
communities: action.payload.communities.map( communities: action.payload.communities.map(
(community) => community.id, (community) => community.id,
), ),
},
}); });
break; break;
} }

View file

@ -1,11 +1,83 @@
import type { Component } from "solid-js"; import { createEffect, createMemo, type Component } from "solid-js";
import { CommunityView } from "../CommunityView"; import { CommunityView } from "../CommunityView";
import { dispatch, state } from "../../store/state";
import { CommunityActionTypes, ICommunity } from "../../store/community";
import { ChannelActionTypes } from "../../store/channel";
import { Channel } from "../../components/Channel";
import { CommunityBar } from "../../components/CommunityBar";
const ChannelView: Component = () => { const ChannelView: Component = () => {
const channelIds = createMemo(() => {
const activeCommunityId = state.community.active;
if (!activeCommunityId) {
return [];
}
const community = state.community.communities[activeCommunityId];
if (!community) {
return [];
}
return community.channels ?? [];
});
const communityInfo = createMemo<ICommunity | undefined>(() => {
const activeCommunityId = state.community.active;
return state.community.communities[activeCommunityId];
});
createEffect(() => {
if (state.community.active) {
dispatch({
type: CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_START,
payload: state.community.active,
});
}
});
const onChannelClick = (id: string) => {
dispatch({
type: ChannelActionTypes.FETCH_CHANNEL_START,
payload: id,
});
dispatch({
type: ChannelActionTypes.SET_ACTIVE_CHANNEL,
payload: id,
});
};
const mapChannel = (channelId: string) => {
const channel = state.channel.channels[channelId];
if (!channel) {
return undefined;
}
return ( return (
<div class="bg-stone-900 w-96 shadow-panel z-20"> <Channel
id={channel.id}
name={channel.name ?? ""}
active={channel.id === state.channel.active}
onChannelClick={onChannelClick}
/>
);
};
return (
<div class="flex flex-row bg-stone-900 w-80 shadow-panel z-20">
<CommunityView /> <CommunityView />
<div></div> <div class="h-full w-full relative">
<CommunityBar
id={communityInfo()?.id}
name={communityInfo()?.name}
description={communityInfo()?.description}
avatar={
"https://img.daisyui.com/images/profile/demo/yellingcat@192.webp"
}
/>
<ul class="h-full list flex flex-col p-2 gap-1 pt-18 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-500 scrollbar-track-gray-800">
{channelIds().map(mapChannel)}
</ul>
</div>
</div> </div>
); );
}; };

View file

@ -1,7 +1,10 @@
import { onMount, type Component } from "solid-js"; import { createMemo, onMount, type Component } from "solid-js";
import { ChannelBar } from "../../components/ChannelBar"; import { ChannelBar } from "../../components/ChannelBar";
import { MessageBar } from "../../components/MessageBar"; import { MessageBar } from "../../components/MessageBar";
import { Message } from "../../components/Message"; import { Message } from "../../components/Message";
import { dispatch, state } from "../../store/state";
import { UserActionTypes } from "../../store/user";
import { IChannel } from "../../store/channel";
const ChatView: Component = () => { const ChatView: Component = () => {
const testMessages = [ const testMessages = [
@ -175,6 +178,11 @@ const ChatView: Component = () => {
}, },
]; ];
const channelInfo = createMemo<IChannel | undefined>(() => {
const activeChannelId = state.channel.active;
return state.channel.channels[activeChannelId];
});
let scrollRef: HTMLUListElement | undefined; let scrollRef: HTMLUListElement | undefined;
onMount(() => { onMount(() => {
if (scrollRef) { if (scrollRef) {
@ -183,16 +191,23 @@ const ChatView: Component = () => {
}); });
const onProfileClick = (userId: string) => { const onProfileClick = (userId: string) => {
console.log(userId); dispatch({
type: UserActionTypes.FETCH_USER_START,
payload: userId,
});
}; };
return ( return (
<div class="bg-stone-800 flex-1 z-0 relative"> <div class="bg-stone-800 flex-1 z-0 relative">
<div class="h-full"> <div class="h-full">
<ChannelBar /> <ChannelBar
id={channelInfo()?.id}
name={channelInfo()?.name}
description={channelInfo()?.description}
/>
<ul <ul
ref={scrollRef} ref={scrollRef}
class="h-full list flex flex-col p-2 pt-16 pb-24 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-500 scrollbar-track-gray-800" class="h-full list flex flex-col p-2 pt-18 pb-24 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-500 scrollbar-track-gray-800"
> >
{testMessages.map((msg) => ( {testMessages.map((msg) => (
<Message <Message

View file

@ -1,6 +1,7 @@
import { createMemo, type Component } from "solid-js"; import { createMemo, type Component } from "solid-js";
import { Community } from "../../components/Community"; import { Community } from "../../components/Community";
import { state } from "../../store/state"; import { dispatch, state } from "../../store/state";
import { CommunityActionTypes } from "../../store/community";
const CommunityView: Component = () => { const CommunityView: Component = () => {
const communityIds = createMemo(() => { const communityIds = createMemo(() => {
@ -18,7 +19,14 @@ const CommunityView: Component = () => {
}); });
const onCommunityClick = (id: string) => { const onCommunityClick = (id: string) => {
console.log(id); dispatch({
type: CommunityActionTypes.FETCH_COMMUNITY_START,
payload: id,
});
dispatch({
type: CommunityActionTypes.SET_ACTIVE_COMMUNITY,
payload: id,
});
}; };
const mapCommunity = (communityId: string) => { const mapCommunity = (communityId: string) => {
@ -34,17 +42,16 @@ const CommunityView: Component = () => {
avatar={ avatar={
"https://img.daisyui.com/images/profile/demo/yellingcat@192.webp" "https://img.daisyui.com/images/profile/demo/yellingcat@192.webp"
} }
active={community.id === state.community.active}
onCommunityClick={onCommunityClick} onCommunityClick={onCommunityClick}
/> />
); );
}; };
return ( return (
<div class="p-3 h-full"> <div class="bg-stone-950 w-18 h-full shadow-panel z-30 flex flex-col p-2 gap-2">
<div class="bg-stone-950 w-20 h-full rounded-full shadow-panel z-30 flex flex-col p-3 gap-3">
{communityIds().map(mapCommunity)} {communityIds().map(mapCommunity)}
</div> </div>
</div>
); );
}; };

View file

@ -1,7 +1,68 @@
import type { Component } from "solid-js"; import { createEffect, createMemo, type Component } from "solid-js";
import { dispatch, state } from "../../store/state";
import { CommunityActionTypes } from "../../store/community";
import { UserActionTypes } from "../../store/user";
import { Member } from "../../components/Member";
const MemberView: Component = () => { const MemberView: Component = () => {
return <div class="bg-stone-900 w-64 shadow-panel z-20"></div>; const memberIds = createMemo(() => {
const activeCommunityId = state.community.active;
if (!activeCommunityId) {
return [];
}
const community = state.community.communities[activeCommunityId];
if (!community) {
return [];
}
return community.members ?? [];
});
createEffect(() => {
if (state.community.active) {
dispatch({
type: CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_START,
payload: state.community.active,
});
}
});
const onMemberClick = (id: string) => {
dispatch({
type: UserActionTypes.FETCH_USER_START,
payload: id,
});
};
const mapMember = (memberId: string) => {
const member = state.user.users[memberId];
if (!member) {
return undefined;
}
return (
<Member
id={member.id}
username={member.username ?? ""}
avatar={
"https://img.daisyui.com/images/profile/demo/yellingcat@192.webp"
}
active={false}
onMemberClick={onMemberClick}
/>
);
};
return (
<div class="bg-stone-900 w-64 shadow-panel z-20 relative">
<div class="h-full">
<ul class="h-full list flex flex-col p-2 gap-1 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-500 scrollbar-track-gray-800">
{memberIds().map(mapMember)}
</ul>
</div>
</div>
);
}; };
export { MemberView }; export { MemberView };