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

@ -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 }