mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:41:08 +02:00
chore: commit remaining changes from recent sessions
- Mana page updates across 12 apps (credit display improvements) - Todo board view editor + view selector components - Docker Hono server base Dockerfile - Matrix web vite config update - Docker compose updates - Feedback types.ts (recovered) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
18fae3b66d
commit
4aa8d870a6
18 changed files with 288 additions and 38 deletions
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { SubscriptionPage } from '@manacore/shared-subscription-ui';
|
||||
import { SubscriptionPage } from '@manacore/subscriptions';
|
||||
import { toastStore as toast } from '@manacore/shared-ui';
|
||||
|
||||
function handleSubscribe(planId: string) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { SubscriptionPage } from '@manacore/shared-subscription-ui';
|
||||
import { SubscriptionPage } from '@manacore/subscriptions';
|
||||
|
||||
function handleSubscribe(planId: string) {
|
||||
console.log('Subscribe to plan:', planId);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { SubscriptionPage } from '@manacore/shared-subscription-ui';
|
||||
import { SubscriptionPage } from '@manacore/subscriptions';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { SubscriptionPage } from '@manacore/shared-subscription-ui';
|
||||
import { SubscriptionPage } from '@manacore/subscriptions';
|
||||
import { toastStore } from '@manacore/shared-ui';
|
||||
|
||||
function handleSubscribe(planId: string) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { SubscriptionPage } from '@manacore/shared-subscription-ui';
|
||||
import { SubscriptionPage } from '@manacore/subscriptions';
|
||||
|
||||
function handleSubscribe(planId: string) {
|
||||
console.log('Subscribe to plan:', planId);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { SubscriptionPage } from '@manacore/shared-subscription-ui';
|
||||
import { SubscriptionPage } from '@manacore/subscriptions';
|
||||
|
||||
function handleSubscribe(planId: string) {
|
||||
console.log('Subscribe to plan:', planId);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
CreditCategory,
|
||||
formatCreditCost,
|
||||
type CreditOperationType,
|
||||
} from '@manacore/credit-operations';
|
||||
} from '@manacore/credits';
|
||||
import { ManaCoreEvents } from '@manacore/shared-utils/analytics';
|
||||
|
||||
let balance = $state<CreditBalance | null>(null);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { SubscriptionPage } from '@manacore/shared-subscription-ui';
|
||||
import { SubscriptionPage } from '@manacore/subscriptions';
|
||||
|
||||
let toastMessage = $state<string | null>(null);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { SubscriptionPage } from '@manacore/shared-subscription-ui';
|
||||
import { SubscriptionPage } from '@manacore/subscriptions';
|
||||
|
||||
function handleSubscribe(planId: string) {
|
||||
alert(`Subscribe to plan: ${planId}\n\nThis would trigger RevenueCat purchase flow.`);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const MANACORE_SHARED_PACKAGES = [
|
|||
'@manacore/shared-auth',
|
||||
'@manacore/shared-auth-ui',
|
||||
'@manacore/shared-branding',
|
||||
'@manacore/shared-subscription-ui',
|
||||
'@manacore/subscriptions',
|
||||
'@manacore/shared-profile-ui',
|
||||
'@manacore/shared-i18n',
|
||||
'@manacore/shared-api-client',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { SubscriptionPage } from '@manacore/shared-subscription-ui';
|
||||
import { SubscriptionPage } from '@manacore/subscriptions';
|
||||
import { toastStore } from '@manacore/shared-ui';
|
||||
|
||||
function handleSubscribe(planId: string) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { SubscriptionPage } from '@manacore/shared-subscription-ui';
|
||||
import { SubscriptionPage } from '@manacore/subscriptions';
|
||||
|
||||
function handleSubscribe(planId: string) {
|
||||
console.log('Subscribe to plan:', planId);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { dndzone, SHADOW_PLACEHOLDER_ITEM_ID, type DndEvent } from 'svelte-dnd-action';
|
||||
import type { LocalBoardView, ViewColumn } from '$lib/data/local-store';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -265,6 +266,17 @@
|
|||
colorPickerColumnId = colorPickerColumnId === columnId ? null : columnId;
|
||||
}
|
||||
|
||||
// ─── Column DnD ────────────────────────────────────────
|
||||
const columnFlipDurationMs = 150;
|
||||
|
||||
function handleColumnDndConsider(e: CustomEvent<DndEvent<ViewColumn>>) {
|
||||
columns = e.detail.items;
|
||||
}
|
||||
|
||||
function handleColumnDndFinalize(e: CustomEvent<DndEvent<ViewColumn>>) {
|
||||
columns = e.detail.items.filter((c) => c.id !== SHADOW_PLACEHOLDER_ITEM_ID);
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
if (!name.trim()) return;
|
||||
onSave({
|
||||
|
|
@ -452,9 +464,37 @@
|
|||
{/if}
|
||||
</p>
|
||||
{:else}
|
||||
<div class="columns-list">
|
||||
{#each columns as col (col.id)}
|
||||
<div
|
||||
class="columns-list"
|
||||
use:dndzone={{
|
||||
items: columns,
|
||||
flipDurationMs: columnFlipDurationMs,
|
||||
dropTargetStyle: {},
|
||||
dropTargetClasses: ['columns-drop-target'],
|
||||
type: 'editor-columns',
|
||||
dragDisabled: !columnsEditable,
|
||||
}}
|
||||
onconsider={handleColumnDndConsider}
|
||||
onfinalize={handleColumnDndFinalize}
|
||||
>
|
||||
{#each columns.filter((c) => c.id !== SHADOW_PLACEHOLDER_ITEM_ID) as col (col.id)}
|
||||
<div class="column-item">
|
||||
<!-- Drag handle -->
|
||||
{#if columnsEditable}
|
||||
<span class="drag-handle" aria-label="Spalte verschieben">
|
||||
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<circle cx="9" cy="5" r="1.5" />
|
||||
<circle cx="15" cy="5" r="1.5" />
|
||||
<circle cx="9" cy="10" r="1.5" />
|
||||
<circle cx="15" cy="10" r="1.5" />
|
||||
<circle cx="9" cy="15" r="1.5" />
|
||||
<circle cx="15" cy="15" r="1.5" />
|
||||
<circle cx="9" cy="20" r="1.5" />
|
||||
<circle cx="15" cy="20" r="1.5" />
|
||||
</svg>
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<!-- Color dot -->
|
||||
<div class="color-dot-wrapper">
|
||||
<button
|
||||
|
|
@ -891,6 +931,36 @@
|
|||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
color: #d1d5db;
|
||||
cursor: grab;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.column-item:hover .drag-handle {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
:global(.dark) .drag-handle {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
:global(.columns-drop-target) {
|
||||
outline: 2px dashed #8b5cf6;
|
||||
outline-offset: -2px;
|
||||
border-radius: 0.625rem;
|
||||
background: rgba(139, 92, 246, 0.05);
|
||||
}
|
||||
|
||||
:global(.dark) .column-item {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { dndzone, SHADOW_PLACEHOLDER_ITEM_ID, type DndEvent } from 'svelte-dnd-action';
|
||||
import type { LocalBoardView } from '$lib/data/local-store';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -7,9 +8,30 @@
|
|||
onSelect: (viewId: string) => void;
|
||||
onCreate?: () => void;
|
||||
onEdit?: (view: LocalBoardView) => void;
|
||||
onReorder?: (viewIds: string[]) => void;
|
||||
}
|
||||
|
||||
let { views, activeViewId, onSelect, onCreate, onEdit }: Props = $props();
|
||||
let { views, activeViewId, onSelect, onCreate, onEdit, onReorder }: Props = $props();
|
||||
|
||||
// Local state for DnD
|
||||
let localViews = $state<LocalBoardView[]>([]);
|
||||
|
||||
$effect(() => {
|
||||
localViews = [...views];
|
||||
});
|
||||
|
||||
const flipDurationMs = 150;
|
||||
|
||||
function handleDndConsider(e: CustomEvent<DndEvent<LocalBoardView>>) {
|
||||
localViews = e.detail.items;
|
||||
}
|
||||
|
||||
function handleDndFinalize(e: CustomEvent<DndEvent<LocalBoardView>>) {
|
||||
localViews = e.detail.items.filter((v) => v.id !== SHADOW_PLACEHOLDER_ITEM_ID);
|
||||
if (onReorder) {
|
||||
onReorder(localViews.map((v) => v.id));
|
||||
}
|
||||
}
|
||||
|
||||
// Context menu state
|
||||
let contextMenuViewId = $state<string | null>(null);
|
||||
|
|
@ -65,8 +87,19 @@
|
|||
|
||||
<div class="view-selector-container">
|
||||
<div class="view-selector">
|
||||
<div class="view-pills-scroll">
|
||||
{#each views as view (view.id)}
|
||||
<div
|
||||
class="view-pills-scroll"
|
||||
use:dndzone={{
|
||||
items: localViews,
|
||||
flipDurationMs,
|
||||
dropTargetStyle: {},
|
||||
type: 'view-pills',
|
||||
morphDisabled: true,
|
||||
}}
|
||||
onconsider={handleDndConsider}
|
||||
onfinalize={handleDndFinalize}
|
||||
>
|
||||
{#each localViews.filter((v) => v.id !== SHADOW_PLACEHOLDER_ITEM_ID) as view (view.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="view-pill"
|
||||
|
|
@ -107,23 +140,23 @@
|
|||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
{#if onCreate}
|
||||
<button type="button" class="view-pill add-pill" onclick={onCreate}>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M12 5v14M5 12h14" />
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if onCreate}
|
||||
<button type="button" class="view-pill add-pill" onclick={onCreate}>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M12 5v14M5 12h14" />
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -90,13 +90,36 @@
|
|||
editingView = null;
|
||||
}
|
||||
|
||||
// Filter state
|
||||
// ─── View Reorder ──────────────────────────────────────
|
||||
async function handleReorderViews(viewIds: string[]) {
|
||||
await boardViewsStore.reorderViews(viewIds);
|
||||
}
|
||||
|
||||
// ─── Filter state ──────────────────────────────────────
|
||||
let filterPriorities = $state<TaskPriority[]>([]);
|
||||
let filterProjectId = $state<string | null>(null);
|
||||
let filterLabelIds = $state<string[]>([]);
|
||||
let filterSearchQuery = $state('');
|
||||
let showFilters = $state(false);
|
||||
|
||||
// Load filter from active view when it changes
|
||||
let previousViewId = $state<string | null>(null);
|
||||
$effect(() => {
|
||||
if (activeView && activeView.id !== previousViewId) {
|
||||
previousViewId = activeView.id;
|
||||
if (activeView.filter) {
|
||||
filterPriorities = (activeView.filter.priorities ?? []) as TaskPriority[];
|
||||
filterProjectId = activeView.filter.projectId ?? null;
|
||||
filterLabelIds = activeView.filter.tagIds ?? [];
|
||||
} else {
|
||||
filterPriorities = [];
|
||||
filterProjectId = null;
|
||||
filterLabelIds = [];
|
||||
}
|
||||
filterSearchQuery = '';
|
||||
}
|
||||
});
|
||||
|
||||
function clearFilters() {
|
||||
filterPriorities = [];
|
||||
filterProjectId = null;
|
||||
|
|
@ -104,6 +127,16 @@
|
|||
filterSearchQuery = '';
|
||||
}
|
||||
|
||||
async function saveFiltersToView() {
|
||||
if (!activeViewId) return;
|
||||
const filter: { projectId?: string; tagIds?: string[]; priorities?: string[] } = {};
|
||||
if (filterProjectId) filter.projectId = filterProjectId;
|
||||
if (filterLabelIds.length > 0) filter.tagIds = filterLabelIds;
|
||||
if (filterPriorities.length > 0) filter.priorities = filterPriorities;
|
||||
const hasFilter = Object.keys(filter).length > 0;
|
||||
await boardViewsStore.updateView(activeViewId, { filter: hasFilter ? filter : undefined });
|
||||
}
|
||||
|
||||
let hasActiveFilters = $derived(
|
||||
filterPriorities.length > 0 ||
|
||||
filterProjectId !== null ||
|
||||
|
|
@ -143,6 +176,7 @@
|
|||
onSelect={handleSelectView}
|
||||
onCreate={handleCreateView}
|
||||
onEdit={handleEditView}
|
||||
onReorder={handleReorderViews}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
|
@ -191,13 +225,50 @@
|
|||
showSearch={true}
|
||||
showLabels={true}
|
||||
/>
|
||||
{#if hasActiveFilters}
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="save-filter-btn"
|
||||
onclick={saveFiltersToView}
|
||||
>
|
||||
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z" />
|
||||
<polyline points="17 21 17 13 7 13 7 21" />
|
||||
<polyline points="7 3 7 8 15 8" />
|
||||
</svg>
|
||||
Filter speichern
|
||||
</button>
|
||||
{#if activeView?.filter}
|
||||
<button
|
||||
type="button"
|
||||
class="clear-saved-filter-btn"
|
||||
onclick={async () => {
|
||||
clearFilters();
|
||||
if (activeViewId) {
|
||||
await boardViewsStore.updateView(activeViewId, { filter: undefined });
|
||||
}
|
||||
}}
|
||||
>
|
||||
Gespeicherten Filter entfernen
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Board Content -->
|
||||
<div class="board-container" class:mobile-bottom-padding={isMobile}>
|
||||
{#if activeView}
|
||||
<BoardViewRenderer view={activeView} />
|
||||
<BoardViewRenderer view={{
|
||||
...activeView,
|
||||
filter: hasActiveFilters ? {
|
||||
projectId: filterProjectId ?? undefined,
|
||||
tagIds: filterLabelIds.length > 0 ? filterLabelIds : undefined,
|
||||
priorities: filterPriorities.length > 0 ? filterPriorities : undefined,
|
||||
} : activeView.filter,
|
||||
}} />
|
||||
{:else if boardViews.value.length === 0}
|
||||
<div class="empty-state">
|
||||
<p class="text-muted-foreground">Board Views werden geladen...</p>
|
||||
|
|
@ -323,6 +394,69 @@
|
|||
color: white;
|
||||
}
|
||||
|
||||
/* ─── Save Filter Button ───────────────────────────────── */
|
||||
.save-filter-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: #8b5cf6;
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
border: 1px solid rgba(139, 92, 246, 0.2);
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.save-filter-btn:hover {
|
||||
background: rgba(139, 92, 246, 0.2);
|
||||
border-color: rgba(139, 92, 246, 0.3);
|
||||
}
|
||||
|
||||
:global(.dark) .save-filter-btn {
|
||||
color: #a78bfa;
|
||||
background: rgba(139, 92, 246, 0.15);
|
||||
border-color: rgba(139, 92, 246, 0.25);
|
||||
}
|
||||
|
||||
:global(.dark) .save-filter-btn:hover {
|
||||
background: rgba(139, 92, 246, 0.25);
|
||||
border-color: rgba(139, 92, 246, 0.35);
|
||||
}
|
||||
|
||||
.clear-saved-filter-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
background: transparent;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.clear-saved-filter-btn:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: #ef4444;
|
||||
border-color: rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
|
||||
:global(.dark) .clear-saved-filter-btn {
|
||||
color: #9ca3af;
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
:global(.dark) .clear-saved-filter-btn:hover {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
border-color: rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
.animate-in {
|
||||
animation: animateIn 0.2s ease-out;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { SubscriptionPage } from '@manacore/shared-subscription-ui';
|
||||
import { SubscriptionPage } from '@manacore/subscriptions';
|
||||
|
||||
function handleSubscribe(planId: string) {
|
||||
console.log('Subscribe to plan:', planId);
|
||||
|
|
|
|||
13
packages/feedback/src/types.ts
Normal file
13
packages/feedback/src/types.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Configuration for creating a feedback service instance
|
||||
*/
|
||||
export interface FeedbackServiceConfig {
|
||||
/** Base API URL for the feedback endpoints */
|
||||
apiUrl: string;
|
||||
/** App identifier for multi-app support */
|
||||
appId: string;
|
||||
/** Function to get the current auth token */
|
||||
getAuthToken: () => Promise<string | null>;
|
||||
/** Optional custom endpoint prefix (default: '/api/v1/feedback') */
|
||||
feedbackEndpoint?: string;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { HelpSearchProps } from '../types.js';
|
||||
import type { SearchResult } from './content';
|
||||
import { createSearcher } from './loader';
|
||||
import type { SearchResult } from '../content';
|
||||
import { createSearcher } from '../loader';
|
||||
|
||||
let { content, translations, placeholder, onResultSelect }: HelpSearchProps = $props();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue