diff --git a/src/lib/components/molecules/Settings.svelte b/src/lib/components/molecules/Settings.svelte
new file mode 100644
index 0000000..30f9776
--- /dev/null
+++ b/src/lib/components/molecules/Settings.svelte
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
diff --git a/src/lib/components/molecules/SettingsProfile.svelte b/src/lib/components/molecules/SettingsProfile.svelte
new file mode 100644
index 0000000..1db938a
--- /dev/null
+++ b/src/lib/components/molecules/SettingsProfile.svelte
@@ -0,0 +1,121 @@
+
+
+
+
+
+
{$_('settings.profile.headline')}
+
+
{$_('settings.profile.avatar.headline')}
+
+
+
+
+
+
+
+
+
+
+
+
+ {$_('settings.profile.bio.label')}
+
+
+
+
+
+
diff --git a/src/lib/i18n/locales/bg.json b/src/lib/i18n/locales/bg.json
index 618593a..4e98e44 100644
--- a/src/lib/i18n/locales/bg.json
+++ b/src/lib/i18n/locales/bg.json
@@ -185,7 +185,34 @@
"label": ""
},
"profile": {
- "label": ""
+ "avatar": {
+ "headline": "",
+ "remove": "",
+ "upload": ""
+ },
+ "bio": {
+ "label": "",
+ "placeholder": ""
+ },
+ "extra": {
+ "content": {
+ "placeholder": ""
+ },
+ "headline": "",
+ "hint": "",
+ "label": {
+ "placeholder": ""
+ }
+ },
+ "headline": "",
+ "label": "",
+ "name": {
+ "label": "",
+ "placeholder": ""
+ },
+ "pronouns": {
+ "label": ""
+ }
},
"ssh_gpg_keys": {
"label": ""
diff --git a/src/lib/i18n/locales/de.json b/src/lib/i18n/locales/de.json
index bea851d..dea5c94 100644
--- a/src/lib/i18n/locales/de.json
+++ b/src/lib/i18n/locales/de.json
@@ -185,7 +185,34 @@
"label": ""
},
"profile": {
- "label": ""
+ "avatar": {
+ "headline": "",
+ "remove": "",
+ "upload": ""
+ },
+ "bio": {
+ "label": "",
+ "placeholder": ""
+ },
+ "extra": {
+ "content": {
+ "placeholder": ""
+ },
+ "headline": "",
+ "hint": "",
+ "label": {
+ "placeholder": ""
+ }
+ },
+ "headline": "",
+ "label": "",
+ "name": {
+ "label": "",
+ "placeholder": ""
+ },
+ "pronouns": {
+ "label": ""
+ }
},
"ssh_gpg_keys": {
"label": ""
diff --git a/src/lib/i18n/locales/en.json b/src/lib/i18n/locales/en.json
index d995f26..76b9861 100644
--- a/src/lib/i18n/locales/en.json
+++ b/src/lib/i18n/locales/en.json
@@ -185,7 +185,34 @@
"label": "Notifications"
},
"profile": {
- "label": "Profile"
+ "avatar": {
+ "headline": "Avatar",
+ "remove": "remove",
+ "upload": "Upload image"
+ },
+ "bio": {
+ "label": "Bio",
+ "placeholder": "Tell us something about you in less than 250 characters..."
+ },
+ "extra": {
+ "content": {
+ "placeholder": "Content"
+ },
+ "headline": "Extra fields",
+ "hint": "Your homepage, age, location, anything you want.",
+ "label": {
+ "placeholder": "Label"
+ }
+ },
+ "headline": "Profile",
+ "label": "Profile",
+ "name": {
+ "label": "Name",
+ "placeholder": "Name others recognize you by"
+ },
+ "pronouns": {
+ "label": "Pronouns"
+ }
},
"ssh_gpg_keys": {
"label": "SSH/GPG Keys"
diff --git a/src/lib/i18n/locales/he.json b/src/lib/i18n/locales/he.json
index 3669218..1fb4b80 100644
--- a/src/lib/i18n/locales/he.json
+++ b/src/lib/i18n/locales/he.json
@@ -185,7 +185,34 @@
"label": ""
},
"profile": {
- "label": ""
+ "avatar": {
+ "headline": "",
+ "remove": "",
+ "upload": ""
+ },
+ "bio": {
+ "label": "",
+ "placeholder": ""
+ },
+ "extra": {
+ "content": {
+ "placeholder": ""
+ },
+ "headline": "",
+ "hint": "",
+ "label": {
+ "placeholder": ""
+ }
+ },
+ "headline": "",
+ "label": "",
+ "name": {
+ "label": "",
+ "placeholder": ""
+ },
+ "pronouns": {
+ "label": ""
+ }
},
"ssh_gpg_keys": {
"label": ""
diff --git a/src/lib/i18n/locales/pl.json b/src/lib/i18n/locales/pl.json
index 85bac85..a60ed41 100644
--- a/src/lib/i18n/locales/pl.json
+++ b/src/lib/i18n/locales/pl.json
@@ -185,7 +185,34 @@
"label": ""
},
"profile": {
- "label": ""
+ "avatar": {
+ "headline": "",
+ "remove": "",
+ "upload": ""
+ },
+ "bio": {
+ "label": "",
+ "placeholder": ""
+ },
+ "extra": {
+ "content": {
+ "placeholder": ""
+ },
+ "headline": "",
+ "hint": "",
+ "label": {
+ "placeholder": ""
+ }
+ },
+ "headline": "",
+ "label": "",
+ "name": {
+ "label": "",
+ "placeholder": ""
+ },
+ "pronouns": {
+ "label": ""
+ }
},
"ssh_gpg_keys": {
"label": ""
diff --git a/stories/molecules/SettingsProfile.stories.ts b/stories/molecules/SettingsProfile.stories.ts
new file mode 100644
index 0000000..2ce3497
--- /dev/null
+++ b/stories/molecules/SettingsProfile.stories.ts
@@ -0,0 +1,26 @@
+/* Stories for SettingsProfile molecule.
+ * Copyright (C) 2024 André Jaenisch
+ * SPDX-FileCopyrightText: 2024 André Jaenisch
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
+ */
+
+import type { Meta, StoryObj } from '@storybook/svelte';
+
+import SettingsProfile from '$lib/components/molecules/SettingsProfile.svelte';
+
+const meta = {
+ title: 'Molecules/SettingsProfile',
+ component: SettingsProfile,
+ tags: ['autodocs']
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Plain: Story = {};
diff --git a/tests/components/molecules/SettingsProfile.test.ts b/tests/components/molecules/SettingsProfile.test.ts
new file mode 100644
index 0000000..64c24c2
--- /dev/null
+++ b/tests/components/molecules/SettingsProfile.test.ts
@@ -0,0 +1,100 @@
+/* Component test for SettingsProfile molecule.
+ * Copyright (C) 2024 André Jaenisch
+ * SPDX-FileCopyrightText: 2024 André Jaenisch
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
+ */
+
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/svelte';
+import { init, locale, register } from 'svelte-i18n';
+
+import SettingsProfile from '../../../src/lib/components/molecules/SettingsProfile.svelte';
+import enMessages from '../../../src/lib/i18n/locales/en.json';
+
+describe('SettingsProfile.svelte', () => {
+ beforeEach(() => {
+ register('en', () => import('../../../src/lib/i18n/locales/en.json'));
+ init({ fallbackLocale: 'en', initialLocale: 'en' });
+ locale.set('en');
+ });
+
+ it('should mount', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ const { container } = render(SettingsProfile);
+
+ // Assert
+ expect(container).toBeTruthy();
+ });
+
+ it('should have an avatar section', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(SettingsProfile);
+
+ // Assert
+ expect(screen.getByText(enMessages.settings.profile.avatar.headline)).toBeInTheDocument();
+ });
+
+ it('should have an avatar upload button', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(SettingsProfile);
+
+ // Assert
+ expect(
+ screen.getByRole('button', { name: enMessages.settings.profile.avatar.upload })
+ ).toBeInTheDocument();
+ });
+
+ it('should have an avatar remove button', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(SettingsProfile);
+
+ // Assert
+ expect(
+ screen.getByRole('button', { name: enMessages.settings.profile.avatar.remove })
+ ).toBeInTheDocument();
+ });
+
+ it('should have a name input', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(SettingsProfile);
+
+ // Assert
+ expect(
+ screen.getByPlaceholderText(enMessages.settings.profile.name.placeholder)
+ ).toBeInTheDocument();
+ });
+
+ it.skip('should have a pronouns input', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(SettingsProfile);
+
+ // Assert
+ expect(
+ screen.getByRole('textbox', { name: enMessages.settings.profile.pronouns.label })
+ ).toBeInTheDocument();
+ });
+});