@axium/contacts 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +157 -0
- package/README.md +3 -0
- package/db.json +131 -0
- package/dist/client/api.d.ts +2 -0
- package/dist/client/api.js +4 -0
- package/dist/client/format.d.ts +28 -0
- package/dist/client/format.js +71 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +2 -0
- package/dist/client/web_hook.d.ts +8 -0
- package/dist/client/web_hook.js +4 -0
- package/dist/common.d.ts +698 -0
- package/dist/common.js +114 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server/api.d.ts +1 -0
- package/dist/server/api.js +138 -0
- package/dist/server/db.d.ts +21 -0
- package/dist/server/db.js +75 -0
- package/dist/server/hooks.d.ts +4 -0
- package/dist/server/hooks.js +8 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/pfp.d.ts +1 -0
- package/dist/server/pfp.js +81 -0
- package/lib/ContactCard.svelte +21 -0
- package/lib/ContactPicture.svelte +46 -0
- package/lib/DateSelect.svelte +25 -0
- package/lib/Discovery.svelte +101 -0
- package/lib/Field.svelte +31 -0
- package/lib/InitForm.svelte +264 -0
- package/lib/List.svelte +37 -0
- package/lib/index.ts +7 -0
- package/lib/tsconfig.json +13 -0
- package/locales/en.json +72 -0
- package/package.json +69 -0
- package/routes/contacts/+layout.ts +10 -0
- package/routes/contacts/+page.svelte +29 -0
- package/routes/contacts/+page.ts +15 -0
- package/routes/contacts/[id]/+layout.ts +12 -0
- package/routes/contacts/[id]/+page.svelte +247 -0
- package/routes/contacts/[id]/edit/+page.svelte +25 -0
- package/routes/contacts/new/+page.svelte +33 -0
- package/routes/contacts/new/+page.ts +11 -0
- package/routes/tsconfig.json +13 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { text } from '@axium/client';
|
|
3
|
+
import { Icon, Popover } from '@axium/client/components';
|
|
4
|
+
import { toast, toastStatus } from '@axium/client/toast';
|
|
5
|
+
import { format, getContact } from '@axium/contacts/client';
|
|
6
|
+
import { ContactPicture, Field } from '@axium/contacts/components';
|
|
7
|
+
import { upload } from 'utilium/dom.js';
|
|
8
|
+
|
|
9
|
+
const { data } = $props();
|
|
10
|
+
const { contact } = data;
|
|
11
|
+
|
|
12
|
+
let hasDefaultPicture = $state(false);
|
|
13
|
+
|
|
14
|
+
async function updatePicture() {
|
|
15
|
+
try {
|
|
16
|
+
const file = await upload('image/*');
|
|
17
|
+
const response = await fetch('/raw/contacts/pfp/' + contact.id, {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: { 'content-type': file.type, 'content-length': file.size.toString() },
|
|
20
|
+
body: file,
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
if (response.headers.get('content-type')?.endsWith('/json')) {
|
|
24
|
+
const error = await response.json();
|
|
25
|
+
throw error.message;
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error('Failed to update contact picture');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
await toast('success', text('contacts.image.toast_updated'));
|
|
31
|
+
} catch (e) {
|
|
32
|
+
await toast('error', e);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<svelte:head>
|
|
38
|
+
<title>{text('contacts.named_title', { name: format.name(contact) })}</title>
|
|
39
|
+
</svelte:head>
|
|
40
|
+
|
|
41
|
+
<div class="contact-actions">
|
|
42
|
+
<a href="/contacts">
|
|
43
|
+
<button class="icon-text">
|
|
44
|
+
<Icon i="arrow-left" />
|
|
45
|
+
<span>{text('contacts.back')}</span>
|
|
46
|
+
</button>
|
|
47
|
+
</a>
|
|
48
|
+
|
|
49
|
+
<span></span>
|
|
50
|
+
|
|
51
|
+
<a href="/contacts/{contact.id}/edit">
|
|
52
|
+
<button class="icon-text">
|
|
53
|
+
<Icon i="pencil" />
|
|
54
|
+
<span>{text('contacts.edit')}</span>
|
|
55
|
+
</button>
|
|
56
|
+
</a>
|
|
57
|
+
|
|
58
|
+
<button class="icon-text">
|
|
59
|
+
<Icon i="trash" />
|
|
60
|
+
<span>{text('contacts.delete')}</span>
|
|
61
|
+
</button>
|
|
62
|
+
|
|
63
|
+
<button class="icon-text">
|
|
64
|
+
<Icon i="file-export" />
|
|
65
|
+
<span>{text('contacts.export')}</span>
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
{#snippet part(i: string, value: string | false | 0 | null | undefined)}
|
|
70
|
+
{#if value}
|
|
71
|
+
<Icon {i} />
|
|
72
|
+
<span>{value}</span>
|
|
73
|
+
{/if}
|
|
74
|
+
{/snippet}
|
|
75
|
+
|
|
76
|
+
<div class="contact-header">
|
|
77
|
+
<div class="contact-image-container">
|
|
78
|
+
<ContactPicture {contact} --size="150px" bind:isDefault={hasDefaultPicture} />
|
|
79
|
+
{#if hasDefaultPicture}
|
|
80
|
+
<Icon i="regular/camera" class="contact-image-menu-toggle" onclick={updatePicture} />
|
|
81
|
+
{:else}
|
|
82
|
+
<Popover>
|
|
83
|
+
{#snippet toggle()}
|
|
84
|
+
<Icon i="regular/camera" class="contact-image-menu-toggle" />
|
|
85
|
+
{/snippet}
|
|
86
|
+
|
|
87
|
+
<div class="menu-item" onclick={updatePicture}>
|
|
88
|
+
<Icon i="upload" />
|
|
89
|
+
<span>{text('contacts.image.update')}</span>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div
|
|
93
|
+
class="menu-item"
|
|
94
|
+
onclick={() =>
|
|
95
|
+
toastStatus(fetch('/raw/contacts/pfp/' + contact.id, { method: 'DELETE' }), text('contacts.image.toast_removed'))}
|
|
96
|
+
>
|
|
97
|
+
<Icon i="trash" />
|
|
98
|
+
<span>{text('contacts.image.remove')}</span>
|
|
99
|
+
</div>
|
|
100
|
+
</Popover>
|
|
101
|
+
{/if}
|
|
102
|
+
</div>
|
|
103
|
+
<span class="contact-name-title">{format.name(contact)}</span>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="contact">
|
|
107
|
+
{@render part('regular/buildings', format.job(contact))}
|
|
108
|
+
|
|
109
|
+
{#if contact.emails.length}
|
|
110
|
+
<Icon i="regular/envelope" />
|
|
111
|
+
<div class="section">
|
|
112
|
+
{#each contact.emails as { email, label, isDefault }}
|
|
113
|
+
<Field text={email} link="mailto:{email}" {label} {isDefault} />
|
|
114
|
+
{/each}
|
|
115
|
+
</div>
|
|
116
|
+
{/if}
|
|
117
|
+
|
|
118
|
+
{#if contact.phones.length}
|
|
119
|
+
<Icon i="phone" />
|
|
120
|
+
<div class="section">
|
|
121
|
+
{#each contact.phones as phone}
|
|
122
|
+
<Field text={format.phone(phone)} link={format.phoneLink(phone)} label={phone.label} isDefault={phone.isDefault} />
|
|
123
|
+
{/each}
|
|
124
|
+
</div>
|
|
125
|
+
{/if}
|
|
126
|
+
|
|
127
|
+
{#if contact.addresses.length}
|
|
128
|
+
<Icon i="regular/location-dot" />
|
|
129
|
+
<div class="section">
|
|
130
|
+
{#each contact.addresses as addr}
|
|
131
|
+
<Field text={format.address(addr)} label={addr.label} isDefault={addr.isDefault} />
|
|
132
|
+
{/each}
|
|
133
|
+
</div>
|
|
134
|
+
{/if}
|
|
135
|
+
|
|
136
|
+
{@render part('cake-candles', format.birthDate(contact))}
|
|
137
|
+
|
|
138
|
+
{#if contact.relationships.length}
|
|
139
|
+
<Icon i="regular/circle-nodes" />
|
|
140
|
+
<div class="section">
|
|
141
|
+
{#each contact.relationships as { to, label }}
|
|
142
|
+
{#await getContact(to) then other}
|
|
143
|
+
<Field text={format.name(other)} {label} link="/contacts/{other.id}" />
|
|
144
|
+
{/await}
|
|
145
|
+
{/each}
|
|
146
|
+
</div>
|
|
147
|
+
{/if}
|
|
148
|
+
|
|
149
|
+
{#if contact.dates.length}
|
|
150
|
+
<Icon i="regular/calendar-day" />
|
|
151
|
+
<div class="section">
|
|
152
|
+
{#each contact.dates as date}
|
|
153
|
+
<Field text={format.date(date)} label={date.label} />
|
|
154
|
+
{/each}
|
|
155
|
+
</div>
|
|
156
|
+
{/if}
|
|
157
|
+
|
|
158
|
+
{#if contact.urls.length}
|
|
159
|
+
<Icon i="link-simple" />
|
|
160
|
+
<div class="section">
|
|
161
|
+
{#each contact.urls as url}
|
|
162
|
+
<Field text={url} link={url} />
|
|
163
|
+
{/each}
|
|
164
|
+
</div>
|
|
165
|
+
{/if}
|
|
166
|
+
|
|
167
|
+
{#if contact.custom.length}
|
|
168
|
+
<Icon i="regular/input-text" />
|
|
169
|
+
<div class="section">
|
|
170
|
+
{#each contact.custom as custom}
|
|
171
|
+
<Field text={custom.value} label={custom.label} />
|
|
172
|
+
{/each}
|
|
173
|
+
</div>
|
|
174
|
+
{/if}
|
|
175
|
+
|
|
176
|
+
{@render part('regular/note', contact.notes)}
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<style>
|
|
180
|
+
.contact-image-container {
|
|
181
|
+
width: 150px;
|
|
182
|
+
height: 150px;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
:global(.contact-image-menu-toggle) {
|
|
186
|
+
float: right;
|
|
187
|
+
position: relative;
|
|
188
|
+
top: -24px;
|
|
189
|
+
|
|
190
|
+
&:hover {
|
|
191
|
+
cursor: pointer;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.contact-name-title {
|
|
196
|
+
margin-left: 2em;
|
|
197
|
+
font-size: 2em;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.contact-header,
|
|
201
|
+
.contact,
|
|
202
|
+
.contact-actions {
|
|
203
|
+
padding: 2em;
|
|
204
|
+
width: 700px;
|
|
205
|
+
|
|
206
|
+
@media (width < 700px) {
|
|
207
|
+
width: 100%;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.contact-header,
|
|
212
|
+
.contact-actions {
|
|
213
|
+
display: flex;
|
|
214
|
+
gap: 1em;
|
|
215
|
+
align-items: center;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.contact-actions a {
|
|
219
|
+
flex: 0 0 auto;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.contact-actions > span {
|
|
223
|
+
flex: 1 1 auto;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.contact {
|
|
227
|
+
display: grid;
|
|
228
|
+
grid-template-columns: 1em 1fr;
|
|
229
|
+
gap: 1em;
|
|
230
|
+
|
|
231
|
+
button.toggle {
|
|
232
|
+
height: 1em;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
:global(.ZodInput) {
|
|
236
|
+
display: flex;
|
|
237
|
+
flex-direction: column;
|
|
238
|
+
gap: 1em;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.section {
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
gap: 0.25em;
|
|
246
|
+
}
|
|
247
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fetchAPI, text } from '@axium/client';
|
|
3
|
+
import { toast } from '@axium/client/toast';
|
|
4
|
+
import { Init } from '@axium/contacts';
|
|
5
|
+
import { InitForm } from '@axium/contacts/components';
|
|
6
|
+
|
|
7
|
+
const { data } = $props();
|
|
8
|
+
|
|
9
|
+
let init = $state<Init>(data.contact);
|
|
10
|
+
|
|
11
|
+
async function save() {
|
|
12
|
+
try {
|
|
13
|
+
const contact = await fetchAPI('PATCH', 'contacts/:id', init, data.contact.id);
|
|
14
|
+
location.pathname = '/contacts/' + contact.id;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
await toast('error', e);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<svelte:head>
|
|
22
|
+
<title>{text('contacts.edit_title')}</title>
|
|
23
|
+
</svelte:head>
|
|
24
|
+
|
|
25
|
+
<InitForm bind:init {save} back="/contacts/{data.contact.id}" />
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fetchAPI, text } from '@axium/client';
|
|
3
|
+
import { toast } from '@axium/client/toast';
|
|
4
|
+
import { Init } from '@axium/contacts';
|
|
5
|
+
import { InitForm } from '@axium/contacts/components';
|
|
6
|
+
|
|
7
|
+
const { data } = $props();
|
|
8
|
+
|
|
9
|
+
let init = $state<Init>({
|
|
10
|
+
emails: [],
|
|
11
|
+
phones: [],
|
|
12
|
+
addresses: [],
|
|
13
|
+
relationships: [],
|
|
14
|
+
urls: [],
|
|
15
|
+
dates: [],
|
|
16
|
+
custom: [],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
async function save() {
|
|
20
|
+
try {
|
|
21
|
+
const contact = await fetchAPI('PUT', 'users/:id/contacts', init, data.session.userId);
|
|
22
|
+
location.pathname = '/contacts/' + contact.id;
|
|
23
|
+
} catch (e) {
|
|
24
|
+
await toast('error', e);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<svelte:head>
|
|
30
|
+
<title>{text('contacts.init.title')}</title>
|
|
31
|
+
</svelte:head>
|
|
32
|
+
|
|
33
|
+
<InitForm bind:init {save} />
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["../tsconfig.json", "../.svelte-kit/tsconfig.json"],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"moduleResolution": "bundler",
|
|
5
|
+
"module": "esnext",
|
|
6
|
+
"noEmit": true,
|
|
7
|
+
"target": "esnext",
|
|
8
|
+
"rootDir": "..",
|
|
9
|
+
"types": ["@sveltejs/kit"]
|
|
10
|
+
},
|
|
11
|
+
"include": ["**/*", "../lib/*"],
|
|
12
|
+
"references": [{ "path": ".." }]
|
|
13
|
+
}
|