Skip to content

Commit 717adad

Browse files
committed
feat: Basic implementation of lists
1 parent 36ccfe6 commit 717adad

File tree

11 files changed

+639
-114
lines changed

11 files changed

+639
-114
lines changed

apps/spuxx-client/package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,18 @@
1212
"license": "MIT",
1313
"devDependencies": {
1414
"@modyfi/vite-plugin-yaml": "1.1.0",
15-
"@tailwindcss/vite": "^4.1.5",
16-
"tailwindcss": "^4.1.5",
17-
"typescript": "^5.7.2",
18-
"vite": "^6.0.0",
19-
"vite-plugin-solid": "^2.11.6",
15+
"@tailwindcss/vite": "4.1.5",
16+
"tailwindcss": "4.1.5",
17+
"typescript": "5.7.2",
18+
"vite": "6.0.0",
19+
"vite-plugin-solid": "2.11.6",
2020
"vite-tsconfig-paths": "5.1.4"
2121
},
2222
"dependencies": {
23-
"@solidjs/router": "^0.15.3",
24-
"@spuxx/browser-utils": "^1.7.0",
25-
"@spuxx/js-utils": "1.3.0",
26-
"@spuxx/solid": "1.3.0",
23+
"@solidjs/router": "0.15.3",
24+
"@spuxx/browser-utils": "1.8.0",
25+
"@spuxx/js-utils": "2.0.1",
26+
"@spuxx/solid": "1.5.0",
2727
"iconify-icon": "2.3.0",
2828
"solid-js": "^1.9.5"
2929
}

apps/spuxx-client/src/assets/locales/de.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,8 @@ main:
33
page:
44
login:
55
login-via-oidc: Anmelden via auth.spuxx.dev
6+
7+
layout:
8+
side-nav:
9+
lists:
10+
title: Listen
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { createListsStore } from '@/stores/lists.store';
2+
import { intl } from '@spuxx/js-utils';
3+
import { ButtonLink, Layout, Sidebar } from '@spuxx/solid';
4+
import { Component, createEffect, For } from 'solid-js';
5+
6+
export const SideNavLists: Component = () => {
7+
const { store, fetchLists } = createListsStore();
8+
9+
createEffect(() => {
10+
fetchLists();
11+
});
12+
13+
return (
14+
<Sidebar.Group
15+
title={intl('layout.side-nav.lists.title')}
16+
icon="mdi:list-box"
17+
>
18+
<For each={store.lists}>
19+
{(list) => (
20+
<ButtonLink
21+
class="decoration-transsparent"
22+
href={`/lists/${list.id}`}
23+
variant="colored"
24+
color="text-default"
25+
active={location.pathname === `/lists/${list.id}`}
26+
onClick={Layout.closeSidebarOnMobile}
27+
icon={list.icon}
28+
>
29+
{list.name}
30+
</ButtonLink>
31+
)}
32+
</For>
33+
</Sidebar.Group>
34+
);
35+
};

apps/spuxx-client/src/layout/side-nav/side-nav.component.tsx

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import { Button, ButtonLink, Layout, Sidebar } from '@spuxx/solid';
22
import { UserAgent } from '@spuxx/browser-utils';
3-
import { For, Show } from 'solid-js';
4-
// import { routes } from '../../routes/routes';
3+
import { Show } from 'solid-js';
4+
import { SideNavLists } from './side-nav-lists.component';
5+
import { LocalStorage } from '@/services/local-storage';
56

67
export const SideNav = () => {
8+
if (LocalStorage.get('sideNavOpen')) Layout.openSidebar();
9+
10+
const handleContentPresentChange = (present: boolean) => {
11+
LocalStorage.set('sideNavOpen', present);
12+
};
13+
714
return (
8-
<Sidebar side="left">
15+
<Sidebar side="left" onContentPresentChange={handleContentPresentChange}>
916
<Sidebar.Toolbar>
1017
<Show when={!UserAgent.isDesktop}>
1118
<Button
@@ -45,21 +52,11 @@ export const SideNav = () => {
4552
color="text-default"
4653
/>
4754
</Sidebar.Toolbar>
48-
{/* <Sidebar.Content>
49-
<nav>
50-
<ul>
51-
<For each={routes}>
52-
{(route) => (
53-
<li>
54-
<ButtonLink class="decoration-transparent" href={route.path}>
55-
{route.path}
56-
</ButtonLink>
57-
</li>
58-
)}
59-
</For>
60-
</ul>
55+
<Sidebar.Content>
56+
<nav class="all-inherit">
57+
<SideNavLists />
6158
</nav>
62-
</Sidebar.Content> */}
59+
</Sidebar.Content>
6360
</Sidebar>
6461
);
6562
};

apps/spuxx-client/src/services/api/icons/icons.endpoints.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { defineEndpoint } from '@spuxx/js-utils';
22

33
export const iconsEndpoints = {
4-
findManyIcons: defineEndpoint({
5-
function: async (query: string): Promise<Response> => {
4+
findManyIcons: defineEndpoint<{ query: string }>({
5+
function: async ({ args, signal }): Promise<Response> => {
66
const response = await fetch(
7-
`https://api.iconify.design/search?query=mdi:${query}`
7+
`https://api.iconify.design/search?query=mdi:${args.query}`,
8+
{
9+
signal,
10+
}
811
);
912
return response;
1013
},

apps/spuxx-client/src/services/api/lists/lists.endpoints.ts

Lines changed: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,42 @@ import type {
1313

1414
export const listsEndpoints = {
1515
findManyLists: defineEndpoint({
16-
function: async (): Promise<Response> => {
16+
function: async ({ signal }): Promise<Response> => {
1717
const { API_URL } = Config.getConfig<AppConfig>();
18-
return fetch(`${API_URL}/toledo/lists`, Api.requestOptions);
18+
return fetch(`${API_URL}/toledo/lists`, {
19+
...Api.requestOptions,
20+
signal,
21+
});
1922
},
2023
transformer: async (response): Promise<List[] | undefined> => {
2124
const json = await response.json();
2225
const lists: List[] = [...json];
2326
return lists;
2427
},
2528
}),
26-
findListById: defineEndpoint({
27-
function: async (id: string): Promise<Response> => {
29+
findListById: defineEndpoint<{ id: string }>({
30+
function: async ({ args, signal }): Promise<Response> => {
2831
const { API_URL } = Config.getConfig<AppConfig>();
29-
return fetch(
30-
`${API_URL}/toledo/lists/${id}?include=items`,
31-
Api.requestOptions
32-
);
32+
return fetch(`${API_URL}/toledo/lists/${args.id}?include=items`, {
33+
...Api.requestOptions,
34+
signal,
35+
});
3336
},
3437
transformer: async (response): Promise<List> => {
3538
const json = await response.json();
3639
const list: List = { ...json };
3740
return list;
3841
},
3942
}),
40-
createList: defineEndpoint({
41-
function: async (list: NewList): Promise<Response> => {
42-
const body = JSON.stringify(list);
43+
createList: defineEndpoint<{ list: NewList }>({
44+
function: async ({ args, signal }): Promise<Response> => {
45+
const body = JSON.stringify(args.list);
4346
const { API_URL } = Config.getConfig<AppConfig>();
4447
return fetch(`${API_URL}/toledo/lists`, {
4548
...Api.requestOptions,
4649
method: 'POST',
4750
body,
51+
signal,
4852
});
4953
},
5054
transformer: async (response): Promise<List> => {
@@ -53,14 +57,15 @@ export const listsEndpoints = {
5357
return list;
5458
},
5559
}),
56-
updateList: defineEndpoint({
57-
function: async (list: UpdatedList): Promise<Response> => {
58-
const body = JSON.stringify(list);
60+
updateList: defineEndpoint<{ list: UpdatedList }>({
61+
function: async ({ args, signal }): Promise<Response> => {
62+
const body = JSON.stringify(args.list);
5963
const { API_URL } = Config.getConfig<AppConfig>();
60-
return fetch(`${API_URL}/toledo/lists/${list.id}?include=items`, {
64+
return fetch(`${API_URL}/toledo/lists/${args.list.id}?include=items`, {
6165
...Api.requestOptions,
6266
method: 'PATCH',
6367
body,
68+
signal,
6469
});
6570
},
6671
transformer: async (response): Promise<List> => {
@@ -69,21 +74,23 @@ export const listsEndpoints = {
6974
return list;
7075
},
7176
}),
72-
deleteList: defineEndpoint({
73-
function: async (id: string): Promise<void> => {
77+
deleteList: defineEndpoint<{ id: string }>({
78+
function: async ({ args, signal }): Promise<void> => {
7479
const { API_URL } = Config.getConfig<AppConfig>();
75-
await fetch(`${API_URL}/toledo/lists/${id}`, {
80+
await fetch(`${API_URL}/toledo/lists/${args.id}`, {
7681
...Api.requestOptions,
7782
method: 'DELETE',
83+
signal,
7884
});
7985
},
8086
}),
81-
generateListInvite: defineEndpoint({
82-
function: async (id: string): Promise<Response> => {
87+
generateListInvite: defineEndpoint<{ id: string }>({
88+
function: async ({ args, signal }): Promise<Response> => {
8389
const { API_URL } = Config.getConfig<AppConfig>();
84-
return fetch(`${API_URL}/toledo/lists/${id}/generate-invite`, {
90+
return fetch(`${API_URL}/toledo/lists/${args.id}/generate-invite`, {
8591
...Api.requestOptions,
8692
method: 'POST',
93+
signal,
8794
});
8895
},
8996
transformer: async (response): Promise<ListInviteLink> => {
@@ -92,23 +99,28 @@ export const listsEndpoints = {
9299
return inviteLink;
93100
},
94101
}),
95-
acceptListInvite: defineEndpoint({
96-
function: async (id: string, code: string): Promise<Response> => {
102+
acceptListInvite: defineEndpoint<{ id: string; code: string }>({
103+
function: async ({ args, signal }): Promise<Response> => {
97104
const { API_URL } = Config.getConfig<AppConfig>();
98-
return fetch(`${API_URL}/toledo/lists/${id}/accept-invite?code=${code}`, {
99-
...Api.requestOptions,
100-
method: 'PUT',
101-
});
105+
return fetch(
106+
`${API_URL}/toledo/lists/${args.id}/accept-invite?code=${args.code}`,
107+
{
108+
...Api.requestOptions,
109+
method: 'PUT',
110+
signal,
111+
}
112+
);
102113
},
103114
}),
104-
createListItem: defineEndpoint({
105-
function: async (listId: string, item: NewListItem): Promise<Response> => {
106-
const body = JSON.stringify(item);
115+
createListItem: defineEndpoint<{ listId: string; item: NewListItem }>({
116+
function: async ({ args, signal }): Promise<Response> => {
117+
const body = JSON.stringify(args.item);
107118
const { API_URL } = Config.getConfig<AppConfig>();
108-
return fetch(`${API_URL}/toledo/lists/${listId}/items`, {
119+
return fetch(`${API_URL}/toledo/lists/${args.listId}/items`, {
109120
...Api.requestOptions,
110121
method: 'POST',
111122
body,
123+
signal,
112124
});
113125
},
114126
transformer: async (response): Promise<ListItem> => {
@@ -117,29 +129,37 @@ export const listsEndpoints = {
117129
return list;
118130
},
119131
}),
120-
updateListItem: defineEndpoint({
121-
function: async (item: ListItem): Promise<Response> => {
122-
const body = JSON.stringify(item);
132+
updateListItem: defineEndpoint<{ item: ListItem }>({
133+
function: async ({ args, signal }): Promise<Response> => {
134+
const body = JSON.stringify(args.item);
123135
const { API_URL } = Config.getConfig<AppConfig>();
124-
return fetch(`${API_URL}/toledo/lists/${item.listId}/items/${item.id}`, {
125-
...Api.requestOptions,
126-
method: 'PATCH',
127-
body,
128-
});
136+
return fetch(
137+
`${API_URL}/toledo/lists/${args.item.listId}/items/${args.item.id}`,
138+
{
139+
...Api.requestOptions,
140+
method: 'PATCH',
141+
body,
142+
signal,
143+
}
144+
);
129145
},
130146
transformer: async (response): Promise<ListItem> => {
131147
const json = await response.json();
132148
const list: ListItem = { ...json };
133149
return list;
134150
},
135151
}),
136-
deleteListItem: defineEndpoint({
137-
function: async (listId: string, itemId: string): Promise<void> => {
152+
deleteListItem: defineEndpoint<{ listId: string; itemId: string }>({
153+
function: async ({ args, signal }): Promise<void> => {
138154
const { API_URL } = Config.getConfig<AppConfig>();
139-
await fetch(`${API_URL}/toledo/lists/${listId}/items/${itemId}`, {
140-
...Api.requestOptions,
141-
method: 'DELETE',
142-
});
155+
await fetch(
156+
`${API_URL}/toledo/lists/${args.listId}/items/${args.itemId}`,
157+
{
158+
...Api.requestOptions,
159+
method: 'DELETE',
160+
signal,
161+
}
162+
);
143163
},
144164
}),
145165
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './local-storage.service';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { LocalStorageMixin } from '@spuxx/browser-utils';
2+
3+
export interface ILocalStorage {
4+
sideNavOpen: boolean;
5+
}
6+
7+
export class LocalStorage extends LocalStorageMixin<ILocalStorage>({
8+
key: 'spuxx-client',
9+
defaultValues: {
10+
sideNavOpen: false,
11+
},
12+
}) {}

apps/spuxx-client/src/services/session/session.service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Api } from '../api';
55
import { Config } from '@spuxx/browser-utils';
66
import { AppConfig } from '@/config/app.config';
77
import { useNavigate } from '@solidjs/router';
8+
import { createListsStore } from '@/stores/lists.store';
89

910
export class SessionService extends ServiceMixin<SessionService>() {
1011
private _session = createSignal<Session | null>(null);
@@ -34,7 +35,7 @@ export class SessionService extends ServiceMixin<SessionService>() {
3435
if (this.session()?.sub) {
3536
return this.session();
3637
}
37-
const session: Session | undefined = await Api.getSession();
38+
const session: Session | undefined = await Api.getSession().promise;
3839
if (!session) return;
3940
Logger.debug(
4041
`Logged in as ${session.preferred_username}.`,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Api } from '@/services/api';
2+
import { List } from '@/services/api/lists/lists.types';
3+
import { HttpRequestStatus } from '@spuxx/js-utils';
4+
import { createStore } from 'solid-js/store';
5+
6+
export function createListsStore() {
7+
const [store, setStore] = createStore<{
8+
lists: List[];
9+
status: HttpRequestStatus;
10+
error: Error | null;
11+
}>({
12+
lists: [],
13+
status: HttpRequestStatus.pending,
14+
error: null,
15+
});
16+
17+
const fetchLists = async () => {
18+
const request = Api.findManyLists();
19+
setStore('status', request.status);
20+
setStore('error', null);
21+
22+
try {
23+
await request.promise;
24+
setStore('lists', request.transformedResult);
25+
setStore('status', HttpRequestStatus.success);
26+
} catch (error) {
27+
setStore('error', error as Error);
28+
setStore('status', HttpRequestStatus.error);
29+
setStore('lists', []);
30+
}
31+
};
32+
33+
return { store, fetchLists };
34+
}

0 commit comments

Comments
 (0)