diff --git a/src/main.tsx b/src/main.tsx
index be4ab51..7687115 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -28,7 +28,7 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
} />
} />
- } />
+ } />
} />
diff --git a/src/pages/CreatePattern.module.css b/src/pages/CreatePattern.module.css
new file mode 100644
index 0000000..3efe96f
--- /dev/null
+++ b/src/pages/CreatePattern.module.css
@@ -0,0 +1,15 @@
+@layer pages {
+ .create_form {
+ border-radius: calc(var(--border-radius) * 2);
+ background-color: var(--color-surface-container);
+ }
+ .form_row {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: calc(var(--spacing) * 3);
+ .pattern_name_input {
+ min-width: 30em;
+ }
+ }
+}
diff --git a/src/pages/CreatePattern.tsx b/src/pages/CreatePattern.tsx
new file mode 100644
index 0000000..649f428
--- /dev/null
+++ b/src/pages/CreatePattern.tsx
@@ -0,0 +1,80 @@
+import { invoke } from '@tauri-apps/api/core';
+import cx from 'clsx';
+import { useSetAtom } from 'jotai';
+import { FC, useActionState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { NotificationType, ToastDuration, useNotification } from '../components/Notifications';
+import { CurrentPatternAtom, Pattern } from '../context/Patterns';
+import styles from './CreatePattern.module.css';
+
+const CreatePattern: FC = () => {
+ const { showToast } = useNotification();
+ const navigate = useNavigate();
+ const loadPattern = useSetAtom(CurrentPatternAtom);
+
+ const [errState, handleFormSubmit] = useActionState(async (state, formData) => {
+ const patternName = formData.get('pattern_name') as string | null;
+
+ if (patternName === null || patternName.length === 0) {
+ showToast(
+ NotificationType.ERROR,
+ 'Please enter a pattern name.',
+ 'material-symbols-light:error-outline',
+ ToastDuration.MEDIUM,
+ );
+ return true;
+ }
+
+ const newPattern = new Pattern();
+ newPattern.name = patternName;
+
+ try {
+ await invoke('save_pattern', { pattern: newPattern });
+ const reloadedPattern = await invoke('get_pattern', { patternId: newPattern.id });
+ if (!reloadedPattern) {
+ showToast(
+ NotificationType.ERROR,
+ 'Failed to reload the created pattern. Please try again.',
+ 'material-symbols-light:error-outline',
+ ToastDuration.MEDIUM,
+ );
+ loadPattern(null);
+ navigate('/library');
+ return true;
+ }
+ loadPattern(reloadedPattern);
+ navigate('./edit');
+ } catch (e) {
+ console.error('[save pattern]', e);
+ loadPattern(null);
+ showToast(
+ NotificationType.ERROR,
+ 'Failed to create pattern. Please try again.',
+ 'material-symbols-light:error-outline',
+ ToastDuration.MEDIUM,
+ );
+ }
+
+ return false;
+ }, false);
+
+ return (
+
+ );
+};
+
+export default CreatePattern;
diff --git a/src/pages/PatternNavigator.tsx b/src/pages/PatternNavigator.tsx
new file mode 100644
index 0000000..9f203d4
--- /dev/null
+++ b/src/pages/PatternNavigator.tsx
@@ -0,0 +1,12 @@
+import { useAtomValue } from 'jotai';
+import { FC } from 'react';
+import { Navigate } from 'react-router-dom';
+import { CurrentPatternAtom } from '../context/Patterns';
+
+const PatternNavigator: FC = () => {
+ const currentPattern = useAtomValue(CurrentPatternAtom);
+
+ return currentPattern === null ? : ;
+};
+
+export default PatternNavigator;