chore: initial project setup with modular architecture
- Add React Native project structure - Configure TypeScript with path aliases - Setup Zustand state management - Integrate Supabase for backend - Implement design system and shared components - Add navigation structure (Auth & App stacks) - Configure versioning tools (husky, commitlint, standard-version) - Setup CI/CD workflows - Add comprehensive documentation
This commit is contained in:
commit
4d48142752
|
|
@ -0,0 +1,28 @@
|
|||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'type-enum': [
|
||||
2,
|
||||
'always',
|
||||
[
|
||||
'feat', // Neues Feature
|
||||
'fix', // Bugfix
|
||||
'docs', // Dokumentation
|
||||
'style', // Formatierung
|
||||
'refactor', // Code-Refactoring
|
||||
'perf', // Performance-Verbesserung
|
||||
'test', // Tests
|
||||
'build', // Build-System
|
||||
'ci', // CI/CD
|
||||
'chore', // Sonstiges
|
||||
'revert', // Revert eines Commits
|
||||
],
|
||||
],
|
||||
'type-case': [2, 'always', 'lower-case'],
|
||||
'type-empty': [2, 'never'],
|
||||
'scope-case': [2, 'always', 'lower-case'],
|
||||
'subject-empty': [2, 'never'],
|
||||
'subject-full-stop': [2, 'never', '.'],
|
||||
'header-max-length': [2, 'always', 100],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# Environment Variables
|
||||
|
||||
# Supabase
|
||||
SUPABASE_URL=https://your-project.supabase.co
|
||||
SUPABASE_ANON_KEY=your-anon-key-here
|
||||
|
||||
# Gitea (NICHT IN GIT COMMITEN!)
|
||||
GITEA_URL=http://192.168.1.142:3000
|
||||
GITEA_TOKEN=ec01d92db7f02dec1089cbb00076d9cbd533fd3f
|
||||
GITEA_USER=Firstly
|
||||
|
||||
# App Configuration
|
||||
NODE_ENV=development
|
||||
API_BASE_URL=http://localhost:3000/api
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: '@react-native',
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-shadow': ['error'],
|
||||
'no-shadow': 'off',
|
||||
'no-undef': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
push:
|
||||
branches: [develop]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test & Lint
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter
|
||||
run: npm run lint
|
||||
|
||||
- name: Type check
|
||||
run: npm run type-check
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
build-ios:
|
||||
name: Build iOS
|
||||
runs-on: macos-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Pods
|
||||
run: |
|
||||
cd ios
|
||||
pod install
|
||||
|
||||
- name: Build iOS
|
||||
run: |
|
||||
cd ios
|
||||
xcodebuild -workspace Fristy.xcworkspace \
|
||||
-scheme Fristy \
|
||||
-configuration Release \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 14' \
|
||||
build
|
||||
|
||||
build-android:
|
||||
name: Build Android
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: Build Android
|
||||
run: |
|
||||
cd android
|
||||
./gradlew assembleRelease
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
- beta
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Type check
|
||||
run: npm run type-check
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npx semantic-release
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
.pnp/
|
||||
.pnp.js
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
*.test.ts.snap
|
||||
*.test.tsx.snap
|
||||
|
||||
# Production
|
||||
build/
|
||||
dist/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.netrc
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# React Native
|
||||
.expo/
|
||||
.expo-shared/
|
||||
|
||||
# iOS
|
||||
ios/Pods/
|
||||
ios/build/
|
||||
*.xcuserstate
|
||||
*.xcworkspace/xcuserdata/
|
||||
ios/.xcode.env.local
|
||||
|
||||
# Android
|
||||
android/app/build/
|
||||
android/.gradle/
|
||||
android/gradle/
|
||||
android/local.properties
|
||||
*.apk
|
||||
*.aab
|
||||
|
||||
# Buck
|
||||
.buckconfig.local
|
||||
.buckd/
|
||||
buck-out/
|
||||
|
||||
# Misc
|
||||
.bundle/
|
||||
*.jsbundle
|
||||
*.bak
|
||||
*.tmp
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# CocoaPods
|
||||
ios/Pods/
|
||||
Podfile.lock
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no -- commitlint --edit ${1}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run lint-staged
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"arrowParens": "avoid",
|
||||
"bracketSpacing": true,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
arrowParens: 'avoid',
|
||||
bracketSameLine: true,
|
||||
bracketSpacing: true,
|
||||
singleQuote: true,
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
};
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
module.exports = {
|
||||
branches: ['main', 'develop', { name: 'beta', prerelease: true }],
|
||||
plugins: [
|
||||
'@semantic-release/commit-analyzer',
|
||||
'@semantic-release/release-notes-generator',
|
||||
[
|
||||
'@semantic-release/changelog',
|
||||
{
|
||||
changelogFile: 'CHANGELOG.md',
|
||||
},
|
||||
],
|
||||
'@semantic-release/npm',
|
||||
[
|
||||
'@semantic-release/git',
|
||||
{
|
||||
assets: ['CHANGELOG.md', 'package.json'],
|
||||
message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
|
||||
},
|
||||
],
|
||||
'@semantic-release/github',
|
||||
],
|
||||
};
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
# Fristy - Vertrags-Management App
|
||||
## Architektur-Dokumentation
|
||||
|
||||
### 📱 Übersicht
|
||||
Eine modulare iOS/Android-App zur Verwaltung aller persönlichen Verträge (Handy, DSL, Netflix, Wasser, etc.)
|
||||
|
||||
### 🏗️ Tech Stack
|
||||
- **Frontend**: React Native (iOS & Android Support)
|
||||
- **Navigation**: React Navigation
|
||||
- **State Management**: Zustand (leichtgewichtig & modular)
|
||||
- **Backend**: Supabase (Auth, Database, Storage)
|
||||
- **Styling**: NativeWind (Tailwind CSS für React Native)
|
||||
- **Icons**: React Native Vector Icons
|
||||
- **Notifications**: React Native Push Notifications
|
||||
|
||||
### 📐 Modulare Architektur
|
||||
|
||||
```
|
||||
app-v0.0.1/
|
||||
├── src/
|
||||
│ ├── modules/ # Feature-Module (unabhängig)
|
||||
│ │ ├── auth/ # Authentifizierung
|
||||
│ │ │ ├── screens/
|
||||
│ │ │ ├── components/
|
||||
│ │ │ ├── hooks/
|
||||
│ │ │ ├── services/
|
||||
│ │ │ └── types/
|
||||
│ │ ├── contracts/ # Vertrags-Verwaltung
|
||||
│ │ │ ├── screens/
|
||||
│ │ │ ├── components/
|
||||
│ │ │ ├── hooks/
|
||||
│ │ │ ├── services/
|
||||
│ │ │ └── types/
|
||||
│ │ ├── dashboard/ # Übersicht
|
||||
│ │ ├── notifications/ # Erinnerungen
|
||||
│ │ ├── profile/ # Benutzerprofil
|
||||
│ │ └── analytics/ # Statistiken (optional)
|
||||
│ ├── shared/ # Gemeinsame Ressourcen
|
||||
│ │ ├── components/ # UI-Komponenten
|
||||
│ │ ├── hooks/ # Wiederverwendbare Hooks
|
||||
│ │ ├── utils/ # Hilfsfunktionen
|
||||
│ │ ├── constants/ # Konstanten
|
||||
│ │ ├── types/ # TypeScript Typen
|
||||
│ │ └── theme/ # Design System
|
||||
│ ├── navigation/ # App-Navigation
|
||||
│ ├── store/ # Globaler State
|
||||
│ └── config/ # Konfiguration
|
||||
├── assets/ # Bilder, Fonts, etc.
|
||||
├── ios/ # iOS-spezifisch
|
||||
├── android/ # Android-spezifisch
|
||||
└── __tests__/ # Tests
|
||||
```
|
||||
|
||||
### 🎯 Core-Module
|
||||
|
||||
#### 1. **Auth-Modul** (`src/modules/auth/`)
|
||||
- Login/Registrierung
|
||||
- Passwort-Reset
|
||||
- Session-Management
|
||||
- Biometrische Authentifizierung (Face ID/Touch ID)
|
||||
|
||||
#### 2. **Contracts-Modul** (`src/modules/contracts/`)
|
||||
- Vertrag hinzufügen/bearbeiten/löschen
|
||||
- Kategorien (Internet, Mobilfunk, Streaming, Versicherungen, etc.)
|
||||
- Dokumente hochladen (PDF-Scans)
|
||||
- Kündigungsfristen-Tracking
|
||||
- Kostenübersicht
|
||||
|
||||
#### 3. **Dashboard-Modul** (`src/modules/dashboard/`)
|
||||
- Übersicht aller Verträge
|
||||
- Monatskosten-Zusammenfassung
|
||||
- Nächste Kündigungsfristen
|
||||
- Schnellaktionen
|
||||
|
||||
#### 4. **Notifications-Modul** (`src/modules/notifications/`)
|
||||
- Push-Benachrichtigungen
|
||||
- Kündigungsfristen-Erinnerungen
|
||||
- Zahlungserinnerungen
|
||||
- Custom Reminder
|
||||
|
||||
#### 5. **Profile-Modul** (`src/modules/profile/`)
|
||||
- Benutzereinstellungen
|
||||
- App-Einstellungen
|
||||
- Datenschutz
|
||||
- Export/Backup
|
||||
|
||||
### 🗄️ Datenmodell
|
||||
|
||||
#### User
|
||||
```typescript
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
```
|
||||
|
||||
#### Contract
|
||||
```typescript
|
||||
interface Contract {
|
||||
id: string;
|
||||
userId: string;
|
||||
name: string; // z.B. "Vodafone Handyvertrag"
|
||||
provider: string; // z.B. "Vodafone"
|
||||
category: ContractCategory;
|
||||
startDate: Date;
|
||||
endDate?: Date;
|
||||
cancellationDeadline?: Date; // Kündigungsfrist
|
||||
noticePeriod: number; // Kündigungsfrist in Monaten
|
||||
cost: number; // Monatliche Kosten
|
||||
billingCycle: 'monthly' | 'quarterly' | 'yearly';
|
||||
autoRenewal: boolean;
|
||||
notes?: string;
|
||||
documents: Document[]; // PDF-Anhänge
|
||||
reminderEnabled: boolean;
|
||||
reminderDays: number; // Tage vor Kündigungsfrist
|
||||
status: 'active' | 'cancelled' | 'expired';
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
```
|
||||
|
||||
#### ContractCategory
|
||||
```typescript
|
||||
enum ContractCategory {
|
||||
MOBILE = 'mobile',
|
||||
INTERNET = 'internet',
|
||||
STREAMING = 'streaming',
|
||||
INSURANCE = 'insurance',
|
||||
UTILITIES = 'utilities', // Strom, Wasser, Gas
|
||||
GYM = 'gym',
|
||||
SUBSCRIPTIONS = 'subscriptions', // Magazine, etc.
|
||||
OTHER = 'other'
|
||||
}
|
||||
```
|
||||
|
||||
### 🔄 State Management (Zustand)
|
||||
|
||||
```typescript
|
||||
// src/store/contractStore.ts
|
||||
interface ContractStore {
|
||||
contracts: Contract[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
|
||||
// Actions
|
||||
fetchContracts: () => Promise<void>;
|
||||
addContract: (contract: Omit<Contract, 'id'>) => Promise<void>;
|
||||
updateContract: (id: string, data: Partial<Contract>) => Promise<void>;
|
||||
deleteContract: (id: string) => Promise<void>;
|
||||
filterByCategory: (category: ContractCategory) => Contract[];
|
||||
getTotalMonthlyCost: () => number;
|
||||
getUpcomingDeadlines: (days: number) => Contract[];
|
||||
}
|
||||
```
|
||||
|
||||
### 🎨 Design System
|
||||
|
||||
#### Farben
|
||||
- Primary: `#6366F1` (Indigo)
|
||||
- Secondary: `#10B981` (Green)
|
||||
- Warning: `#F59E0B` (Amber)
|
||||
- Danger: `#EF4444` (Red)
|
||||
- Background: `#F9FAFB` (Light Gray)
|
||||
- Text: `#111827` (Dark Gray)
|
||||
|
||||
#### Kategorien-Farben
|
||||
- Mobile: `#3B82F6` (Blue)
|
||||
- Internet: `#8B5CF6` (Purple)
|
||||
- Streaming: `#EC4899` (Pink)
|
||||
- Insurance: `#10B981` (Green)
|
||||
- Utilities: `#F59E0B` (Amber)
|
||||
|
||||
### 🚀 Entwicklungs-Roadmap
|
||||
|
||||
#### Phase 1: MVP (Minimum Viable Product)
|
||||
- [ ] User Authentication (Email/Password)
|
||||
- [ ] Verträge erstellen/bearbeiten/löschen
|
||||
- [ ] Dashboard mit Übersicht
|
||||
- [ ] Kategorisierung
|
||||
- [ ] Basis-Benachrichtigungen
|
||||
|
||||
#### Phase 2: Enhanced Features
|
||||
- [ ] Dokumenten-Upload
|
||||
- [ ] Erweiterte Benachrichtigungen
|
||||
- [ ] Kostenanalyse & Charts
|
||||
- [ ] Export-Funktion (PDF/CSV)
|
||||
- [ ] Biometrische Authentifizierung
|
||||
|
||||
#### Phase 3: Advanced
|
||||
- [ ] OCR für automatisches Auslesen von Verträgen
|
||||
- [ ] Kündigungs-Assistent (automatische Kündigungen)
|
||||
- [ ] Vergleichsportal-Integration
|
||||
- [ ] Familie/Team-Sharing
|
||||
- [ ] Cloud-Backup
|
||||
|
||||
### 🔐 Sicherheit
|
||||
- Alle sensiblen Daten verschlüsselt
|
||||
- Row Level Security (RLS) in Supabase
|
||||
- Biometrische Authentifizierung
|
||||
- Automatischer Logout nach Inaktivität
|
||||
- Keine lokale Speicherung von Passwörtern
|
||||
|
||||
### 📱 Navigation-Struktur
|
||||
|
||||
```
|
||||
Stack Navigator
|
||||
├── Auth Stack (nicht eingeloggt)
|
||||
│ ├── Login
|
||||
│ ├── Register
|
||||
│ └── ForgotPassword
|
||||
└── App Stack (eingeloggt)
|
||||
└── Tab Navigator
|
||||
├── Dashboard (Home)
|
||||
├── Contracts (Liste)
|
||||
├── Add Contract
|
||||
├── Notifications
|
||||
└── Profile
|
||||
```
|
||||
|
||||
### 🧪 Testing-Strategie
|
||||
- Unit Tests: Services & Utils
|
||||
- Integration Tests: Stores & Hooks
|
||||
- E2E Tests: Kritische User Flows
|
||||
- Snapshot Tests: UI Components
|
||||
|
||||
### 📦 Deployment
|
||||
- **iOS**: TestFlight → App Store
|
||||
- **Android**: Google Play Console
|
||||
- **CI/CD**: GitHub Actions
|
||||
- **Versionierung**: Semantic Versioning
|
||||
|
||||
### 🔧 Modulare Erweiterbarkeit
|
||||
|
||||
**Neues Feature hinzufügen:**
|
||||
1. Neuen Ordner in `src/modules/` erstellen
|
||||
2. Eigene screens, components, services implementieren
|
||||
3. In Navigation einbinden
|
||||
4. Optional: Eigener Store
|
||||
|
||||
**Feature entfernen:**
|
||||
1. Module-Ordner löschen
|
||||
2. Navigation-Einträge entfernen
|
||||
3. Store-Referenzen entfernen
|
||||
|
||||
**Vorteile dieser Architektur:**
|
||||
- ✅ Jedes Modul ist unabhängig
|
||||
- ✅ Einfaches Testing einzelner Module
|
||||
- ✅ Team-Arbeit möglich (jeder an anderem Modul)
|
||||
- ✅ Features können aktiviert/deaktiviert werden
|
||||
- ✅ Code-Wiederverwendung durch shared/
|
||||
- ✅ Skalierbar für große Apps
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { RootNavigation } from './src/navigation';
|
||||
import { authService } from './src/modules/auth/services/authService';
|
||||
import { useAuthStore } from './src/store';
|
||||
|
||||
function App(): React.JSX.Element {
|
||||
const { setLoading } = useAuthStore();
|
||||
|
||||
useEffect(() => {
|
||||
// Session wiederherstellen beim App-Start
|
||||
const initAuth = async () => {
|
||||
setLoading(true);
|
||||
await authService.restoreSession();
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
initAuth();
|
||||
|
||||
// Auth State Listener
|
||||
const { data: subscription } = authService.onAuthStateChange((session) => {
|
||||
if (session) {
|
||||
// Session aktiv
|
||||
console.log('User authenticated');
|
||||
} else {
|
||||
// Keine Session
|
||||
console.log('User logged out');
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
subscription?.subscription?.unsubscribe();
|
||||
};
|
||||
}, [setLoading]);
|
||||
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<RootNavigation />
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# Changelog
|
||||
|
||||
Alle bedeutenden Änderungen an diesem Projekt werden in dieser Datei dokumentiert.
|
||||
|
||||
Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.0.0/),
|
||||
und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Geplant
|
||||
- Auth Screens (Login, Register, Passwort Reset)
|
||||
- Contract Management Screens
|
||||
- Dashboard mit Statistiken
|
||||
- Push-Benachrichtigungen
|
||||
- Dokumenten-Upload
|
||||
|
||||
## [0.0.1] - 2025-11-17
|
||||
|
||||
### Hinzugefügt
|
||||
- Initiales Projekt-Setup
|
||||
- Modulare Architektur-Struktur
|
||||
- TypeScript-Konfiguration mit Path-Aliases
|
||||
- Shared Components (Button, Input, ContractCard)
|
||||
- Design System (Colors, Spacing, Typography)
|
||||
- Zustand State Management (Auth & Contracts)
|
||||
- Supabase Integration
|
||||
- Services Layer (Auth & Contract Services)
|
||||
- Navigation Structure (Auth Stack & App Tabs)
|
||||
- Vollständige TypeScript-Type-Definitionen
|
||||
- Dokumentation (Architecture, Getting Started, README)
|
||||
|
||||
### Technologie-Stack
|
||||
- React Native 0.73.0
|
||||
- TypeScript 5.3.3
|
||||
- Zustand 4.4.7
|
||||
- Supabase 2.38.4
|
||||
- React Navigation 6.x
|
||||
|
||||
[Unreleased]: http://192.168.1.142:3000/Firstly/fristy/compare/v0.0.1...HEAD
|
||||
[0.0.1]: http://192.168.1.142:3000/Firstly/fristy/releases/tag/v0.0.1
|
||||
|
|
@ -0,0 +1,300 @@
|
|||
# Fristy - Getting Started Guide
|
||||
|
||||
## 🚀 Installation & Setup
|
||||
|
||||
### Voraussetzungen
|
||||
- Node.js >= 18
|
||||
- npm >= 9
|
||||
- Xcode (für iOS)
|
||||
- Android Studio (für Android)
|
||||
- CocoaPods (für iOS): `sudo gem install cocoapods`
|
||||
|
||||
### 1. Dependencies installieren
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 2. iOS Setup
|
||||
|
||||
```bash
|
||||
cd ios
|
||||
pod install
|
||||
cd ..
|
||||
```
|
||||
|
||||
### 3. Supabase konfigurieren
|
||||
|
||||
1. Gehe zu [supabase.com](https://supabase.com) und erstelle ein neues Projekt
|
||||
2. Erstelle die folgende Tabellen-Struktur:
|
||||
|
||||
**Users Tabelle** (wird automatisch von Supabase Auth erstellt)
|
||||
|
||||
**Contracts Tabelle:**
|
||||
```sql
|
||||
CREATE TABLE contracts (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
provider TEXT NOT NULL,
|
||||
category TEXT NOT NULL,
|
||||
start_date TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
end_date TIMESTAMP WITH TIME ZONE,
|
||||
cancellation_deadline TIMESTAMP WITH TIME ZONE,
|
||||
notice_period INTEGER NOT NULL,
|
||||
cost NUMERIC(10,2) NOT NULL,
|
||||
billing_cycle TEXT NOT NULL,
|
||||
auto_renewal BOOLEAN DEFAULT false,
|
||||
notes TEXT,
|
||||
documents JSONB DEFAULT '[]',
|
||||
reminder_enabled BOOLEAN DEFAULT true,
|
||||
reminder_days INTEGER DEFAULT 30,
|
||||
status TEXT DEFAULT 'active',
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Index für bessere Performance
|
||||
CREATE INDEX contracts_user_id_idx ON contracts(user_id);
|
||||
CREATE INDEX contracts_status_idx ON contracts(status);
|
||||
CREATE INDEX contracts_cancellation_deadline_idx ON contracts(cancellation_deadline);
|
||||
|
||||
-- Row Level Security aktivieren
|
||||
ALTER TABLE contracts ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Policy: Benutzer können nur ihre eigenen Verträge sehen
|
||||
CREATE POLICY "Users can view own contracts"
|
||||
ON contracts FOR SELECT
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
-- Policy: Benutzer können nur ihre eigenen Verträge erstellen
|
||||
CREATE POLICY "Users can insert own contracts"
|
||||
ON contracts FOR INSERT
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- Policy: Benutzer können nur ihre eigenen Verträge aktualisieren
|
||||
CREATE POLICY "Users can update own contracts"
|
||||
ON contracts FOR UPDATE
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
-- Policy: Benutzer können nur ihre eigenen Verträge löschen
|
||||
CREATE POLICY "Users can delete own contracts"
|
||||
ON contracts FOR DELETE
|
||||
USING (auth.uid() = user_id);
|
||||
```
|
||||
|
||||
3. Kopiere deine Supabase Credentials:
|
||||
- Project URL
|
||||
- Anon Key
|
||||
|
||||
4. Erstelle `.env` Datei im Root-Verzeichnis:
|
||||
```env
|
||||
SUPABASE_URL=https://your-project.supabase.co
|
||||
SUPABASE_ANON_KEY=your-anon-key-here
|
||||
```
|
||||
|
||||
5. Aktualisiere `src/config/index.ts` mit deinen Credentials
|
||||
|
||||
### 4. App starten
|
||||
|
||||
**iOS:**
|
||||
```bash
|
||||
npm run ios
|
||||
```
|
||||
|
||||
**Android:**
|
||||
```bash
|
||||
npm run android
|
||||
```
|
||||
|
||||
**Metro Bundler (Development Server):**
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
## 📁 Projekt-Struktur
|
||||
|
||||
```
|
||||
app-v0.0.1/
|
||||
├── src/
|
||||
│ ├── modules/ # Feature-Module
|
||||
│ │ ├── auth/ # Authentifizierung
|
||||
│ │ ├── contracts/ # Vertrags-Verwaltung
|
||||
│ │ ├── dashboard/ # Übersicht
|
||||
│ │ ├── notifications/ # Erinnerungen
|
||||
│ │ └── profile/ # Benutzerprofil
|
||||
│ ├── shared/ # Gemeinsame Ressourcen
|
||||
│ │ ├── components/ # UI-Komponenten
|
||||
│ │ ├── hooks/ # Custom Hooks
|
||||
│ │ ├── utils/ # Hilfsfunktionen
|
||||
│ │ ├── constants/ # Konstanten
|
||||
│ │ ├── types/ # TypeScript Typen
|
||||
│ │ └── theme/ # Design System
|
||||
│ ├── navigation/ # Navigation
|
||||
│ ├── store/ # Zustand State Management
|
||||
│ └── config/ # Konfiguration
|
||||
├── assets/ # Bilder, Fonts
|
||||
├── ios/ # iOS-spezifisch
|
||||
├── android/ # Android-spezifisch
|
||||
└── App.tsx # Entry Point
|
||||
```
|
||||
|
||||
## 🔧 Nächste Schritte
|
||||
|
||||
### Phase 1: MVP Features entwickeln
|
||||
|
||||
1. **Auth Screens erstellen**
|
||||
- Login Screen
|
||||
- Register Screen
|
||||
- Passwort Reset
|
||||
|
||||
2. **Contract Screens erstellen**
|
||||
- Verträge Liste
|
||||
- Vertrag hinzufügen
|
||||
- Vertrag bearbeiten
|
||||
- Vertrag Details
|
||||
|
||||
3. **Dashboard erstellen**
|
||||
- Übersicht aller Verträge
|
||||
- Monatliche Kosten
|
||||
- Anstehende Kündigungsfristen
|
||||
|
||||
4. **Notifications implementieren**
|
||||
- Push-Benachrichtigungen Setup
|
||||
- Erinnerungen für Kündigungsfristen
|
||||
|
||||
### Neues Feature hinzufügen
|
||||
|
||||
1. Erstelle einen neuen Ordner in `src/modules/`
|
||||
2. Struktur:
|
||||
```
|
||||
src/modules/mein-feature/
|
||||
├── screens/
|
||||
├── components/
|
||||
├── hooks/
|
||||
├── services/
|
||||
└── types/
|
||||
```
|
||||
3. Implementiere deine Screens und Komponenten
|
||||
4. Füge Navigation in `src/navigation/RootNavigation.tsx` hinzu
|
||||
5. Optional: Erstelle einen Store in `src/store/`
|
||||
|
||||
### Komponenten verwenden
|
||||
|
||||
```tsx
|
||||
import { Button, Input, ContractCard } from '@shared/components';
|
||||
|
||||
// Button
|
||||
<Button
|
||||
title="Speichern"
|
||||
onPress={handleSave}
|
||||
variant="primary"
|
||||
size="md"
|
||||
/>
|
||||
|
||||
// Input
|
||||
<Input
|
||||
label="Vertragsname"
|
||||
placeholder="z.B. Vodafone"
|
||||
value={name}
|
||||
onChangeText={setName}
|
||||
error={errors.name}
|
||||
/>
|
||||
|
||||
// ContractCard
|
||||
<ContractCard
|
||||
contract={contract}
|
||||
onPress={() => navigateToDetail(contract.id)}
|
||||
/>
|
||||
```
|
||||
|
||||
### Store verwenden
|
||||
|
||||
```tsx
|
||||
import { useContractStore } from '@store';
|
||||
|
||||
const MyComponent = () => {
|
||||
const { contracts, addContract, getTotalMonthlyCost } = useContractStore();
|
||||
|
||||
const totalCost = getTotalMonthlyCost();
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text>Verträge: {contracts.length}</Text>
|
||||
<Text>Monatliche Kosten: {totalCost.toFixed(2)}€</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
```bash
|
||||
# Tests ausführen
|
||||
npm test
|
||||
|
||||
# Type-Checking
|
||||
npm run type-check
|
||||
|
||||
# Linting
|
||||
npm run lint
|
||||
```
|
||||
|
||||
## 📱 Build & Deployment
|
||||
|
||||
### iOS
|
||||
|
||||
1. Öffne `ios/Fristy.xcworkspace` in Xcode
|
||||
2. Wähle dein Team unter "Signing & Capabilities"
|
||||
3. Archive erstellen: Product → Archive
|
||||
4. Hochladen zu TestFlight/App Store
|
||||
|
||||
### Android
|
||||
|
||||
```bash
|
||||
# Debug APK
|
||||
cd android
|
||||
./gradlew assembleDebug
|
||||
|
||||
# Release APK
|
||||
./gradlew assembleRelease
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
**Metro Bundler startet nicht:**
|
||||
```bash
|
||||
npm start -- --reset-cache
|
||||
```
|
||||
|
||||
**iOS Build-Fehler:**
|
||||
```bash
|
||||
cd ios
|
||||
pod deintegrate
|
||||
pod install
|
||||
cd ..
|
||||
npm run ios
|
||||
```
|
||||
|
||||
**Android Build-Fehler:**
|
||||
```bash
|
||||
cd android
|
||||
./gradlew clean
|
||||
cd ..
|
||||
npm run android
|
||||
```
|
||||
|
||||
## 📚 Weitere Ressourcen
|
||||
|
||||
- [React Native Docs](https://reactnative.dev/docs/getting-started)
|
||||
- [Supabase Docs](https://supabase.com/docs)
|
||||
- [Zustand Docs](https://docs.pmnd.rs/zustand)
|
||||
- [React Navigation](https://reactnavigation.org/docs/getting-started)
|
||||
|
||||
## 💡 Tipps
|
||||
|
||||
1. **Modulare Entwicklung**: Jedes Feature sollte unabhängig funktionieren
|
||||
2. **TypeScript nutzen**: Typsicherheit hilft Fehler zu vermeiden
|
||||
3. **State Management**: Nutze Zustand für globalen State
|
||||
4. **Komponenten wiederverwenden**: Nutze `@shared/components`
|
||||
5. **Tests schreiben**: Besonders für Business-Logik wichtig
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
# Gitea Configuration
|
||||
|
||||
## Server Details
|
||||
- **URL**: http://192.168.1.142:3000
|
||||
- **Organization/User**: Firstly
|
||||
- **Token**: Gespeichert in `.env` (nicht in Git!)
|
||||
|
||||
## Repository Setup
|
||||
|
||||
### Initial Push to Gitea
|
||||
|
||||
```bash
|
||||
# 1. Remote hinzufügen
|
||||
git remote add origin http://192.168.1.142:3000/Firstly/fristy.git
|
||||
|
||||
# 2. Initial Commit (falls noch nicht gemacht)
|
||||
git add .
|
||||
git commit -m "chore: initial project setup"
|
||||
|
||||
# 3. Push zu Gitea
|
||||
git push -u origin main
|
||||
|
||||
# 4. Develop Branch erstellen
|
||||
git checkout -b develop
|
||||
git push -u origin develop
|
||||
```
|
||||
|
||||
### Authentifizierung
|
||||
|
||||
**Option 1: HTTPS mit Token**
|
||||
```bash
|
||||
# Token als Passwort verwenden
|
||||
git remote set-url origin http://<username>:<token>@192.168.1.142:3000/Firstly/fristy.git
|
||||
```
|
||||
|
||||
**Option 2: Git Credential Helper**
|
||||
```bash
|
||||
# Token speichern
|
||||
git config credential.helper store
|
||||
# Beim nächsten Push Token eingeben
|
||||
```
|
||||
|
||||
**Option 3: SSH (empfohlen für lokales Netzwerk)**
|
||||
```bash
|
||||
# SSH-Key generieren
|
||||
ssh-keygen -t ed25519 -C "your-email@example.com"
|
||||
|
||||
# Public Key zu Gitea hinzufügen
|
||||
# Unter: http://192.168.1.142:3000/user/settings/keys
|
||||
|
||||
# Remote auf SSH umstellen
|
||||
git remote set-url origin git@192.168.1.142:Firstly/fristy.git
|
||||
```
|
||||
|
||||
## Gitea-spezifische Features
|
||||
|
||||
### Branch Protection
|
||||
|
||||
1. Gehe zu Repository Settings
|
||||
2. Branches → Protected Branches
|
||||
3. Schütze `main` und `develop`:
|
||||
- ✅ Require pull request reviews before merging
|
||||
- ✅ Require status checks to pass
|
||||
- ✅ Include administrators
|
||||
|
||||
### Webhooks (für CI/CD)
|
||||
|
||||
1. Settings → Webhooks
|
||||
2. Add Webhook → Gitea
|
||||
3. Payload URL: `http://your-ci-server/webhook`
|
||||
4. Events:
|
||||
- ✅ Push
|
||||
- ✅ Pull Request
|
||||
- ✅ Release
|
||||
|
||||
### Repository Visibility
|
||||
|
||||
- **Private**: Nur für Team sichtbar
|
||||
- **Public**: Öffentlich zugänglich
|
||||
|
||||
## CI/CD mit Gitea Actions
|
||||
|
||||
Gitea unterstützt GitHub Actions-kompatible Workflows!
|
||||
|
||||
### `.gitea/workflows/ci.yml`
|
||||
|
||||
```yaml
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm run type-check
|
||||
- run: npm test
|
||||
```
|
||||
|
||||
## Team-Zugriff
|
||||
|
||||
### Collaborators hinzufügen
|
||||
|
||||
1. Repository → Settings → Collaborators
|
||||
2. Add Collaborator
|
||||
3. Rechte festlegen:
|
||||
- **Read**: Nur lesen
|
||||
- **Write**: Lesen + Schreiben
|
||||
- **Admin**: Volle Rechte
|
||||
|
||||
## Nützliche Gitea-Befehle
|
||||
|
||||
```bash
|
||||
# Status prüfen
|
||||
git remote -v
|
||||
|
||||
# Änderungen pushen
|
||||
git push origin feature/mein-feature
|
||||
|
||||
# Pull Request via CLI (gh-cli oder tea)
|
||||
tea pr create --title "Mein Feature" --body "Beschreibung"
|
||||
|
||||
# Releases erstellen
|
||||
git tag -a v0.1.0 -m "Release v0.1.0"
|
||||
git push origin v0.1.0
|
||||
```
|
||||
|
||||
## Gitea CLI Tool (tea)
|
||||
|
||||
```bash
|
||||
# Installation
|
||||
brew install tea # macOS
|
||||
# oder: https://gitea.com/gitea/tea
|
||||
|
||||
# Login
|
||||
tea login add
|
||||
|
||||
# Repository clonen
|
||||
tea clone Firstly/fristy
|
||||
|
||||
# Issues erstellen
|
||||
tea issues create
|
||||
|
||||
# Pull Requests
|
||||
tea pr list
|
||||
tea pr create
|
||||
```
|
||||
|
||||
## Backup-Strategie
|
||||
|
||||
### Lokales Backup
|
||||
|
||||
```bash
|
||||
# Kompletter Clone mit allen Branches
|
||||
git clone --mirror http://192.168.1.142:3000/Firstly/fristy.git
|
||||
```
|
||||
|
||||
### Automatisches Backup
|
||||
|
||||
```bash
|
||||
# Cron-Job für tägliches Backup
|
||||
0 2 * * * git -C /path/to/backup/fristy.git fetch --all
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### SSL-Zertifikat-Fehler
|
||||
|
||||
```bash
|
||||
# Für lokales Netzwerk (nur Development!)
|
||||
git config --global http.sslVerify false
|
||||
```
|
||||
|
||||
### Token-Authentifizierung
|
||||
|
||||
```bash
|
||||
# Token in URL
|
||||
git clone http://token@192.168.1.142:3000/Firstly/fristy.git
|
||||
|
||||
# Oder in .netrc speichern
|
||||
# ~/.netrc
|
||||
machine 192.168.1.142
|
||||
login username
|
||||
password ec01d92db7f02dec1089cbb00076d9cbd533fd3f
|
||||
```
|
||||
|
||||
### Push wird abgelehnt
|
||||
|
||||
```bash
|
||||
# Force Push vermeiden!
|
||||
# Stattdessen:
|
||||
git pull --rebase origin main
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
✅ **DO**
|
||||
- Token in `.env` speichern (nicht in Git!)
|
||||
- Branch Protection für main/develop
|
||||
- Pull Requests für alle Features
|
||||
- Issues für Bug-Tracking
|
||||
- Releases mit Tags
|
||||
|
||||
❌ **DON'T**
|
||||
- Token in Code commiten
|
||||
- Direkt auf main pushen
|
||||
- Force Push auf shared branches
|
||||
- Große Binärdateien committen
|
||||
|
||||
## Repository-Links
|
||||
|
||||
- **Repository**: http://192.168.1.142:3000/Firstly/fristy
|
||||
- **Issues**: http://192.168.1.142:3000/Firstly/fristy/issues
|
||||
- **Pull Requests**: http://192.168.1.142:3000/Firstly/fristy/pulls
|
||||
- **Releases**: http://192.168.1.142:3000/Firstly/fristy/releases
|
||||
- **Settings**: http://192.168.1.142:3000/Firstly/fristy/settings
|
||||
|
||||
## Quick Commands
|
||||
|
||||
```bash
|
||||
# Repository initialisieren und pushen
|
||||
git init
|
||||
git add .
|
||||
git commit -m "chore: initial project setup"
|
||||
git remote add origin http://192.168.1.142:3000/Firstly/fristy.git
|
||||
git push -u origin main
|
||||
|
||||
# Develop Branch
|
||||
git checkout -b develop
|
||||
git push -u origin develop
|
||||
|
||||
# Feature entwickeln
|
||||
git checkout -b feature/auth-screens
|
||||
# ... entwickeln ...
|
||||
git push origin feature/auth-screens
|
||||
# Dann: Pull Request in Gitea erstellen
|
||||
```
|
||||
|
|
@ -0,0 +1,423 @@
|
|||
# Git Workflow Guide
|
||||
|
||||
## Branch-Strategie
|
||||
|
||||
### Main Branches
|
||||
|
||||
```
|
||||
main (production)
|
||||
└── develop (development)
|
||||
```
|
||||
|
||||
- **main**: Produktions-Branch, immer stabil und deploybar
|
||||
- **develop**: Entwicklungs-Branch, Integration aller Features
|
||||
|
||||
### Supporting Branches
|
||||
|
||||
#### Feature Branches
|
||||
```
|
||||
feature/feature-name
|
||||
```
|
||||
|
||||
Von: `develop`
|
||||
Merge zu: `develop`
|
||||
|
||||
**Beispiele:**
|
||||
- `feature/user-authentication`
|
||||
- `feature/contract-crud`
|
||||
- `feature/dashboard-stats`
|
||||
|
||||
#### Release Branches
|
||||
```
|
||||
release/v1.0.0
|
||||
```
|
||||
|
||||
Von: `develop`
|
||||
Merge zu: `main` und `develop`
|
||||
|
||||
**Verwendung:**
|
||||
- Vorbereitung eines neuen Releases
|
||||
- Bug-Fixes für das Release
|
||||
- Version-Bumps
|
||||
- CHANGELOG aktualisieren
|
||||
|
||||
#### Hotfix Branches
|
||||
```
|
||||
hotfix/critical-bug-name
|
||||
```
|
||||
|
||||
Von: `main`
|
||||
Merge zu: `main` und `develop`
|
||||
|
||||
**Verwendung:**
|
||||
- Kritische Bugs in Production
|
||||
- Schnelle Fixes ohne auf Release zu warten
|
||||
|
||||
## Workflow-Prozess
|
||||
|
||||
### 1. Neues Feature entwickeln
|
||||
|
||||
```bash
|
||||
# Von develop starten
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
|
||||
# Feature-Branch erstellen
|
||||
git checkout -b feature/mein-feature
|
||||
|
||||
# Entwickeln...
|
||||
git add .
|
||||
git commit -m "feat(module): beschreibung"
|
||||
|
||||
# Push zum Remote
|
||||
git push origin feature/mein-feature
|
||||
|
||||
# Pull Request erstellen auf GitHub/GitLab
|
||||
# Nach Review: Merge zu develop
|
||||
```
|
||||
|
||||
### 2. Release vorbereiten
|
||||
|
||||
```bash
|
||||
# Release-Branch erstellen
|
||||
git checkout develop
|
||||
git checkout -b release/v0.2.0
|
||||
|
||||
# Version bumpen
|
||||
npm run release:minor
|
||||
|
||||
# Oder manuell:
|
||||
# - package.json version aktualisieren
|
||||
# - CHANGELOG.md aktualisieren
|
||||
# - iOS/Android Versions aktualisieren
|
||||
|
||||
git push origin release/v0.2.0
|
||||
|
||||
# Nach Tests: Merge zu main und develop
|
||||
git checkout main
|
||||
git merge release/v0.2.0
|
||||
git tag -a v0.2.0 -m "Release v0.2.0"
|
||||
git push origin main --tags
|
||||
|
||||
git checkout develop
|
||||
git merge release/v0.2.0
|
||||
git push origin develop
|
||||
|
||||
# Release-Branch löschen
|
||||
git branch -d release/v0.2.0
|
||||
```
|
||||
|
||||
### 3. Hotfix durchführen
|
||||
|
||||
```bash
|
||||
# Hotfix-Branch erstellen
|
||||
git checkout main
|
||||
git checkout -b hotfix/critical-login-bug
|
||||
|
||||
# Fix implementieren
|
||||
git add .
|
||||
git commit -m "fix(auth): resolve critical login issue"
|
||||
|
||||
# Version bumpen (PATCH)
|
||||
npm run release:patch
|
||||
|
||||
# Push
|
||||
git push origin hotfix/critical-login-bug
|
||||
|
||||
# Merge zu main
|
||||
git checkout main
|
||||
git merge hotfix/critical-login-bug
|
||||
git tag -a v0.2.1 -m "Hotfix v0.2.1"
|
||||
git push origin main --tags
|
||||
|
||||
# Merge zu develop
|
||||
git checkout develop
|
||||
git merge hotfix/critical-login-bug
|
||||
git push origin develop
|
||||
|
||||
# Hotfix-Branch löschen
|
||||
git branch -d hotfix/critical-login-bug
|
||||
```
|
||||
|
||||
## Commit-Guidelines
|
||||
|
||||
### Format
|
||||
|
||||
```
|
||||
type(scope): subject
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer]
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
- **feat**: Neues Feature
|
||||
- **fix**: Bugfix
|
||||
- **docs**: Dokumentation
|
||||
- **style**: Code-Formatierung
|
||||
- **refactor**: Code-Refactoring
|
||||
- **perf**: Performance-Optimierung
|
||||
- **test**: Tests
|
||||
- **build**: Build-System
|
||||
- **ci**: CI/CD
|
||||
- **chore**: Sonstiges (Dependencies, etc.)
|
||||
- **revert**: Revert eines Commits
|
||||
|
||||
### Scope
|
||||
|
||||
Module/Bereich des Projekts:
|
||||
- `auth`
|
||||
- `contracts`
|
||||
- `dashboard`
|
||||
- `notifications`
|
||||
- `profile`
|
||||
- `ui`
|
||||
- `api`
|
||||
- `store`
|
||||
|
||||
### Subject
|
||||
|
||||
- Imperativ, präsens: "add" nicht "added" oder "adds"
|
||||
- Kein Punkt am Ende
|
||||
- Kleinbuchstaben
|
||||
- Max. 50 Zeichen
|
||||
|
||||
### Beispiele
|
||||
|
||||
```bash
|
||||
# Gute Commits
|
||||
git commit -m "feat(auth): add login screen with email validation"
|
||||
git commit -m "fix(contracts): resolve date parsing error"
|
||||
git commit -m "docs(readme): update installation instructions"
|
||||
git commit -m "refactor(store): simplify contract selectors"
|
||||
git commit -m "perf(dashboard): optimize contract list rendering"
|
||||
|
||||
# Breaking Change
|
||||
git commit -m "feat(api)!: change authentication flow
|
||||
|
||||
BREAKING CHANGE: New auth flow requires token refresh"
|
||||
|
||||
# Mit Body
|
||||
git commit -m "fix(auth): prevent duplicate login requests
|
||||
|
||||
Added debouncing to login button to prevent
|
||||
multiple simultaneous requests"
|
||||
```
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
### 1. PR erstellen
|
||||
|
||||
```markdown
|
||||
## Beschreibung
|
||||
Kurze Beschreibung der Änderungen
|
||||
|
||||
## Typ der Änderung
|
||||
- [ ] Bugfix
|
||||
- [ ] Neues Feature
|
||||
- [ ] Breaking Change
|
||||
- [ ] Dokumentation
|
||||
|
||||
## Checklist
|
||||
- [ ] Code folgt Projekt-Style-Guide
|
||||
- [ ] Tests hinzugefügt/aktualisiert
|
||||
- [ ] Dokumentation aktualisiert
|
||||
- [ ] CHANGELOG.md aktualisiert
|
||||
- [ ] Keine Merge-Konflikte
|
||||
```
|
||||
|
||||
### 2. Review-Prozess
|
||||
|
||||
1. CI/CD-Pipeline muss grün sein
|
||||
2. Code-Review von mindestens 1 Entwickler
|
||||
3. Alle Kommentare müssen aufgelöst sein
|
||||
4. Keine Breaking Changes ohne Diskussion
|
||||
|
||||
### 3. Merge-Strategie
|
||||
|
||||
**Feature → Develop:**
|
||||
- Squash & Merge (alle Commits zu einem zusammenfassen)
|
||||
|
||||
**Release/Hotfix → Main:**
|
||||
- Merge Commit (History beibehalten)
|
||||
|
||||
## Tagging
|
||||
|
||||
### Format
|
||||
|
||||
```
|
||||
v{MAJOR}.{MINOR}.{PATCH}[-{PRE-RELEASE}]
|
||||
```
|
||||
|
||||
### Beispiele
|
||||
|
||||
```bash
|
||||
# Production Releases
|
||||
git tag -a v1.0.0 -m "Release v1.0.0"
|
||||
git tag -a v1.1.0 -m "Release v1.1.0 - Added export feature"
|
||||
git tag -a v1.1.1 -m "Hotfix v1.1.1 - Fixed critical bug"
|
||||
|
||||
# Pre-Releases
|
||||
git tag -a v1.0.0-alpha.1 -m "Alpha Release 1"
|
||||
git tag -a v1.0.0-beta.1 -m "Beta Release 1"
|
||||
git tag -a v1.0.0-rc.1 -m "Release Candidate 1"
|
||||
|
||||
# Push tags
|
||||
git push origin --tags
|
||||
```
|
||||
|
||||
## Nützliche Git-Befehle
|
||||
|
||||
### Branch-Management
|
||||
|
||||
```bash
|
||||
# Alle Branches anzeigen
|
||||
git branch -a
|
||||
|
||||
# Remote-Branches aktualisieren
|
||||
git fetch --prune
|
||||
|
||||
# Branch löschen
|
||||
git branch -d branch-name
|
||||
git push origin --delete branch-name
|
||||
|
||||
# Zu anderem Branch wechseln
|
||||
git checkout branch-name
|
||||
git switch branch-name # neuer Befehl
|
||||
```
|
||||
|
||||
### Änderungen verwalten
|
||||
|
||||
```bash
|
||||
# Status anzeigen
|
||||
git status
|
||||
|
||||
# Änderungen stagen
|
||||
git add .
|
||||
git add file.txt
|
||||
|
||||
# Commit erstellen
|
||||
git commit -m "message"
|
||||
|
||||
# Letzten Commit ändern
|
||||
git commit --amend
|
||||
|
||||
# Änderungen verwerfen
|
||||
git restore file.txt
|
||||
git restore .
|
||||
```
|
||||
|
||||
### History
|
||||
|
||||
```bash
|
||||
# Log anzeigen
|
||||
git log
|
||||
git log --oneline
|
||||
git log --graph --oneline --all
|
||||
|
||||
# Unterschiede anzeigen
|
||||
git diff
|
||||
git diff branch1 branch2
|
||||
```
|
||||
|
||||
### Rebase (vorsichtig verwenden!)
|
||||
|
||||
```bash
|
||||
# Feature-Branch mit develop synchronisieren
|
||||
git checkout feature/mein-feature
|
||||
git rebase develop
|
||||
|
||||
# Interaktives Rebase (Commits aufräumen)
|
||||
git rebase -i HEAD~3
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ DO
|
||||
|
||||
- Regelmäßig commits machen
|
||||
- Aussagekräftige Commit-Messages
|
||||
- Feature-Branches aktuell halten (regelmäßig von develop pullen)
|
||||
- Pull Requests klein halten (< 400 Zeilen)
|
||||
- Tests vor dem Push ausführen
|
||||
- Code-Reviews ernst nehmen
|
||||
|
||||
### ❌ DON'T
|
||||
|
||||
- Direkt auf main/develop pushen
|
||||
- Force-Push auf shared branches
|
||||
- Große Binary-Dateien committen
|
||||
- Secrets/API-Keys committen
|
||||
- Merge-Konflikte ignorieren
|
||||
- WIP-Commits ohne aufräumen
|
||||
|
||||
## Automatisierung
|
||||
|
||||
### Husky Hooks
|
||||
|
||||
**Pre-Commit:**
|
||||
- Linting
|
||||
- Formatierung
|
||||
- Tests
|
||||
|
||||
**Commit-Msg:**
|
||||
- Commit-Message-Validierung
|
||||
|
||||
**Pre-Push:**
|
||||
- Tests
|
||||
- Build
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
**CI (Pull Requests):**
|
||||
- Linting
|
||||
- Type-Checking
|
||||
- Tests
|
||||
- Build
|
||||
|
||||
**Release (main branch):**
|
||||
- Automatisches Versioning
|
||||
- CHANGELOG generieren
|
||||
- GitHub Release erstellen
|
||||
- NPM Publish (optional)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Merge-Konflikte
|
||||
|
||||
```bash
|
||||
# Konflikt anzeigen
|
||||
git status
|
||||
|
||||
# Datei manuell editieren und Konflikt-Marker entfernen
|
||||
# <<<<<<<, =======, >>>>>>>
|
||||
|
||||
# Nach dem Lösen
|
||||
git add conflicted-file.txt
|
||||
git commit
|
||||
```
|
||||
|
||||
### Commit rückgängig machen
|
||||
|
||||
```bash
|
||||
# Letzten Commit rückgängig (Änderungen behalten)
|
||||
git reset --soft HEAD~1
|
||||
|
||||
# Letzten Commit rückgängig (Änderungen verwerfen)
|
||||
git reset --hard HEAD~1
|
||||
|
||||
# Commit revert (neuer Commit)
|
||||
git revert HEAD
|
||||
```
|
||||
|
||||
### Branch wiederherstellen
|
||||
|
||||
```bash
|
||||
# Gelöschten Branch finden
|
||||
git reflog
|
||||
|
||||
# Branch wiederherstellen
|
||||
git checkout -b recovered-branch <commit-hash>
|
||||
```
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
# Fristy
|
||||
|
||||
Eine moderne, modulare App zur Verwaltung aller persönlichen Verträge.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 📱 **Vertrags-Management**: Alle Verträge an einem Ort
|
||||
- 🔔 **Kündigungs-Erinnerungen**: Nie wieder eine Frist verpassen
|
||||
- 💰 **Kosten-Übersicht**: Monatliche und jährliche Ausgaben im Blick
|
||||
- 📄 **Dokumente**: PDF-Uploads für Verträge
|
||||
- 🔐 **Sicher**: End-to-End verschlüsselt
|
||||
- 🎨 **Modern**: Schönes, intuitives Design
|
||||
- 🔄 **Cross-Platform**: iOS & Android
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
```bash
|
||||
# Dependencies installieren
|
||||
npm install
|
||||
|
||||
# iOS
|
||||
npm run ios
|
||||
|
||||
# Android
|
||||
npm run android
|
||||
```
|
||||
|
||||
Siehe [GETTING_STARTED.md](./GETTING_STARTED.md) für detaillierte Setup-Anleitung.
|
||||
|
||||
## 📋 Projekt Status
|
||||
|
||||
**Version**: 0.0.1 (Early Development)
|
||||
|
||||
**Aktuelle Phase**: Grundstruktur & Setup
|
||||
|
||||
**Nächste Schritte**:
|
||||
- [ ] Auth Screens implementieren
|
||||
- [ ] Contract CRUD Screens
|
||||
- [ ] Dashboard mit Statistiken
|
||||
- [ ] Push-Benachrichtigungen
|
||||
|
||||
## 🏗️ Architektur
|
||||
|
||||
- **Framework**: React Native
|
||||
- **State**: Zustand
|
||||
- **Backend**: Supabase
|
||||
- **Navigation**: React Navigation
|
||||
- **Language**: TypeScript
|
||||
|
||||
Siehe [ARCHITECTURE.md](./ARCHITECTURE.md) für detaillierte Architektur-Dokumentation.
|
||||
|
||||
## 📱 Unterstützte Plattformen
|
||||
|
||||
- ✅ iOS 13+
|
||||
- ✅ Android 8.0+
|
||||
|
||||
## 🤝 Entwicklung
|
||||
|
||||
```bash
|
||||
# Development Server starten
|
||||
npm start
|
||||
|
||||
# Type Checking
|
||||
npm run type-check
|
||||
|
||||
# Linting
|
||||
npm run lint
|
||||
|
||||
# Tests
|
||||
npm test
|
||||
```
|
||||
|
||||
## 📄 Lizenz
|
||||
|
||||
Proprietary - Alle Rechte vorbehalten
|
||||
|
||||
## 👨💻 Autor
|
||||
|
||||
Fristy Team
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
# Versioning Strategy
|
||||
|
||||
## Semantic Versioning (SemVer)
|
||||
|
||||
Dieses Projekt folgt [Semantic Versioning 2.0.0](https://semver.org/lang/de/)
|
||||
|
||||
### Format: MAJOR.MINOR.PATCH
|
||||
|
||||
```
|
||||
1.2.3
|
||||
│ │ └─── PATCH: Bugfixes, keine Breaking Changes
|
||||
│ └───── MINOR: Neue Features, abwärtskompatibel
|
||||
└─────── MAJOR: Breaking Changes, nicht abwärtskompatibel
|
||||
```
|
||||
|
||||
### Version-Nummern
|
||||
|
||||
#### MAJOR (X.0.0)
|
||||
Erhöhen wenn:
|
||||
- Breaking Changes an der API
|
||||
- Fundamental neue Architektur
|
||||
- Entfernung von Features
|
||||
- Nicht-kompatible Änderungen
|
||||
|
||||
**Beispiele:**
|
||||
- `1.0.0` → `2.0.0`: Neue Auth-Struktur, alte Tokens funktionieren nicht mehr
|
||||
- `2.0.0` → `3.0.0`: Kompletter UI-Rewrite
|
||||
|
||||
#### MINOR (0.X.0)
|
||||
Erhöhen wenn:
|
||||
- Neue Features hinzugefügt
|
||||
- Neue Screens/Module
|
||||
- Abwärtskompatible Funktionalität
|
||||
|
||||
**Beispiele:**
|
||||
- `0.1.0` → `0.2.0`: Neues Notifications-Modul
|
||||
- `1.0.0` → `1.1.0`: Export-Funktion hinzugefügt
|
||||
- `1.1.0` → `1.2.0`: Dokumenten-Upload implementiert
|
||||
|
||||
#### PATCH (0.0.X)
|
||||
Erhöhen wenn:
|
||||
- Bugfixes
|
||||
- Performance-Verbesserungen
|
||||
- UI-Anpassungen ohne neue Features
|
||||
- Dependency-Updates
|
||||
|
||||
**Beispiele:**
|
||||
- `0.1.0` → `0.1.1`: Login-Fehler behoben
|
||||
- `1.0.0` → `1.0.1`: Crash beim Laden behoben
|
||||
- `1.0.1` → `1.0.2`: Performance optimiert
|
||||
|
||||
### Pre-Release Versionen
|
||||
|
||||
#### Alpha (0.0.x-alpha.y)
|
||||
- Sehr frühe Entwicklungsphase
|
||||
- Instabil, viele Bugs erwartet
|
||||
- Nur für interne Tests
|
||||
|
||||
```
|
||||
0.0.1-alpha.1
|
||||
0.0.1-alpha.2
|
||||
```
|
||||
|
||||
#### Beta (0.x.0-beta.y)
|
||||
- Feature-komplett
|
||||
- Testing-Phase
|
||||
- Kann noch Bugs haben
|
||||
|
||||
```
|
||||
0.1.0-beta.1
|
||||
0.1.0-beta.2
|
||||
1.0.0-beta.1
|
||||
```
|
||||
|
||||
#### Release Candidate (x.x.x-rc.y)
|
||||
- Bereit für Release
|
||||
- Nur kritische Bugfixes
|
||||
- Letzte Tests vor Production
|
||||
|
||||
```
|
||||
1.0.0-rc.1
|
||||
1.0.0-rc.2
|
||||
```
|
||||
|
||||
### Build Metadata
|
||||
|
||||
Optional für zusätzliche Informationen:
|
||||
|
||||
```
|
||||
1.0.0+20231117
|
||||
1.0.0+build.123
|
||||
1.0.0-beta.1+exp.sha.5114f85
|
||||
```
|
||||
|
||||
## Version Timeline für Fristy
|
||||
|
||||
### Phase 1: Initial Development (0.0.x)
|
||||
```
|
||||
0.0.1 - Projekt-Setup, Architektur
|
||||
0.0.2 - Auth Screens
|
||||
0.0.3 - Contract CRUD
|
||||
0.0.4 - Dashboard
|
||||
```
|
||||
|
||||
### Phase 2: MVP (0.x.0)
|
||||
```
|
||||
0.1.0 - Alpha Release (interne Tests)
|
||||
0.2.0 - Beta Release (geschlossene Beta-Tester)
|
||||
0.3.0 - Release Candidate
|
||||
```
|
||||
|
||||
### Phase 3: Public Release (1.0.0)
|
||||
```
|
||||
1.0.0 - Erster öffentlicher Release
|
||||
1.1.0 - Dokumenten-Upload
|
||||
1.2.0 - Erweiterte Statistiken
|
||||
1.3.0 - Export-Funktion
|
||||
```
|
||||
|
||||
### Phase 4: Advanced Features (2.0.0)
|
||||
```
|
||||
2.0.0 - Neues Backend, OCR-Feature
|
||||
2.1.0 - Familie/Team-Sharing
|
||||
2.2.0 - Kündigungs-Assistent
|
||||
```
|
||||
|
||||
## Version in Code
|
||||
|
||||
### package.json
|
||||
```json
|
||||
{
|
||||
"version": "0.0.1"
|
||||
}
|
||||
```
|
||||
|
||||
### App-Anzeige
|
||||
```typescript
|
||||
// src/shared/constants/index.ts
|
||||
export const APP_VERSION = '0.0.1';
|
||||
export const BUILD_NUMBER = '1';
|
||||
```
|
||||
|
||||
### Native Code
|
||||
|
||||
**iOS (Info.plist):**
|
||||
```xml
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
```
|
||||
|
||||
**Android (build.gradle):**
|
||||
```gradle
|
||||
versionName "0.0.1"
|
||||
versionCode 1
|
||||
```
|
||||
|
||||
## Git Workflow
|
||||
|
||||
### Branch-Strategie
|
||||
|
||||
```
|
||||
main (production)
|
||||
├── develop (development)
|
||||
│ ├── feature/user-auth
|
||||
│ ├── feature/contract-crud
|
||||
│ └── feature/dashboard
|
||||
├── release/v1.0.0
|
||||
└── hotfix/critical-bug
|
||||
```
|
||||
|
||||
### Commit-Message Format
|
||||
|
||||
```
|
||||
type(scope): subject
|
||||
|
||||
[optional body]
|
||||
[optional footer]
|
||||
```
|
||||
|
||||
**Types:**
|
||||
- `feat`: Neues Feature
|
||||
- `fix`: Bugfix
|
||||
- `docs`: Dokumentation
|
||||
- `style`: Formatierung, keine Code-Änderung
|
||||
- `refactor`: Code-Refactoring
|
||||
- `test`: Tests hinzufügen/ändern
|
||||
- `chore`: Build-Prozess, Dependencies
|
||||
|
||||
**Beispiele:**
|
||||
```
|
||||
feat(auth): implement login screen
|
||||
fix(contracts): resolve date parsing issue
|
||||
docs(readme): update installation steps
|
||||
refactor(store): simplify contract selectors
|
||||
```
|
||||
|
||||
### Release-Prozess
|
||||
|
||||
1. **Feature entwickeln:**
|
||||
```bash
|
||||
git checkout develop
|
||||
git checkout -b feature/mein-feature
|
||||
# ... entwickeln ...
|
||||
git commit -m "feat(module): beschreibung"
|
||||
git push origin feature/mein-feature
|
||||
```
|
||||
|
||||
2. **Release vorbereiten:**
|
||||
```bash
|
||||
git checkout develop
|
||||
git checkout -b release/v0.1.0
|
||||
# Version-Nummern aktualisieren
|
||||
# CHANGELOG.md aktualisieren
|
||||
git commit -m "chore(release): bump version to 0.1.0"
|
||||
```
|
||||
|
||||
3. **Release durchführen:**
|
||||
```bash
|
||||
git checkout main
|
||||
git merge release/v0.1.0
|
||||
git tag -a v0.1.0 -m "Release v0.1.0"
|
||||
git push origin main --tags
|
||||
```
|
||||
|
||||
4. **Hotfix (falls nötig):**
|
||||
```bash
|
||||
git checkout main
|
||||
git checkout -b hotfix/critical-bug
|
||||
# ... fix ...
|
||||
git commit -m "fix(auth): critical security issue"
|
||||
git checkout main
|
||||
git merge hotfix/critical-bug
|
||||
git tag -a v0.1.1 -m "Hotfix v0.1.1"
|
||||
```
|
||||
|
||||
## Version-Updates
|
||||
|
||||
### Automatisch mit npm
|
||||
|
||||
```bash
|
||||
# PATCH: 0.0.1 → 0.0.2
|
||||
npm version patch
|
||||
|
||||
# MINOR: 0.0.1 → 0.1.0
|
||||
npm version minor
|
||||
|
||||
# MAJOR: 0.1.0 → 1.0.0
|
||||
npm version major
|
||||
|
||||
# Pre-Release
|
||||
npm version prerelease --preid=alpha
|
||||
npm version prerelease --preid=beta
|
||||
npm version prerelease --preid=rc
|
||||
```
|
||||
|
||||
### Manuell
|
||||
|
||||
1. `package.json` - version
|
||||
2. `src/shared/constants/index.ts` - APP_VERSION
|
||||
3. `ios/Fristy/Info.plist` - CFBundleShortVersionString
|
||||
4. `android/app/build.gradle` - versionName
|
||||
5. `CHANGELOG.md` - neue Version dokumentieren
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ DO
|
||||
- Version-Bump bei jedem Release
|
||||
- CHANGELOG.md aktuell halten
|
||||
- Semantic Versioning strikt befolgen
|
||||
- Tags für alle Releases
|
||||
- Build-Number bei jedem Build erhöhen
|
||||
|
||||
### ❌ DON'T
|
||||
- Version-Nummern überspringen
|
||||
- MAJOR-Bump für kleine Changes
|
||||
- Ohne Tag releasen
|
||||
- CHANGELOG vernachlässigen
|
||||
- Inkonsistente Versionen (iOS ≠ Android)
|
||||
|
||||
## Tools & Automation
|
||||
|
||||
### Empfohlene Tools
|
||||
|
||||
**standard-version:**
|
||||
```bash
|
||||
npm install --save-dev standard-version
|
||||
|
||||
# In package.json
|
||||
"scripts": {
|
||||
"release": "standard-version",
|
||||
"release:minor": "standard-version --release-as minor",
|
||||
"release:major": "standard-version --release-as major"
|
||||
}
|
||||
```
|
||||
|
||||
**commitlint:**
|
||||
```bash
|
||||
npm install --save-dev @commitlint/cli @commitlint/config-conventional
|
||||
```
|
||||
|
||||
**husky:**
|
||||
```bash
|
||||
npm install --save-dev husky
|
||||
npx husky install
|
||||
```
|
||||
|
||||
## App Store / Play Store
|
||||
|
||||
### Build Numbers
|
||||
|
||||
**iOS:**
|
||||
- Version: `1.0.0` (sichtbar für User)
|
||||
- Build: `1`, `2`, `3`, ... (stetig steigend)
|
||||
|
||||
**Android:**
|
||||
- versionName: `1.0.0` (sichtbar)
|
||||
- versionCode: `1`, `2`, `3`, ... (Integer, stetig steigend)
|
||||
|
||||
### Release-Zyklus
|
||||
|
||||
```
|
||||
Development → TestFlight/Internal Testing → Production
|
||||
↓ ↓ ↓
|
||||
0.1.0-beta 0.1.0-rc.1 1.0.0
|
||||
```
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
- **Aktuell**: `0.0.1` (Initial Setup)
|
||||
- **Nächster Minor**: `0.1.0` (MVP mit allen Basis-Features)
|
||||
- **Nächster Major**: `1.0.0` (Public Release)
|
||||
- **Format**: `MAJOR.MINOR.PATCH`
|
||||
- **Workflow**: Git Flow mit develop/main
|
||||
- **Commits**: Conventional Commits
|
||||
- **Changelog**: Keep a Changelog Format
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
# Versionsverwaltung Setup - Quick Start
|
||||
|
||||
## 🚀 Schnellstart
|
||||
|
||||
### 1. Dependencies installieren
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Dies installiert automatisch alle Versionierungs-Tools:
|
||||
- husky (Git Hooks)
|
||||
- commitlint (Commit-Message-Validierung)
|
||||
- standard-version (Automatisches Versioning)
|
||||
- lint-staged (Pre-Commit Linting)
|
||||
|
||||
### 2. Husky initialisieren
|
||||
|
||||
```bash
|
||||
npm run prepare
|
||||
```
|
||||
|
||||
Dies aktiviert die Git Hooks.
|
||||
|
||||
### 3. Git Repository initialisieren (falls noch nicht geschehen)
|
||||
|
||||
```bash
|
||||
git init
|
||||
git add .
|
||||
git commit -m "chore: initial project setup"
|
||||
```
|
||||
|
||||
**Wichtig:** Der Commit wird automatisch validiert!
|
||||
|
||||
## 📝 Commit-Guidelines
|
||||
|
||||
### Commit-Format (wird automatisch geprüft!)
|
||||
|
||||
```
|
||||
type(scope): subject
|
||||
```
|
||||
|
||||
**Erlaubte Types:**
|
||||
- `feat` - Neues Feature
|
||||
- `fix` - Bugfix
|
||||
- `docs` - Dokumentation
|
||||
- `style` - Formatierung
|
||||
- `refactor` - Code-Refactoring
|
||||
- `perf` - Performance
|
||||
- `test` - Tests
|
||||
- `chore` - Sonstiges
|
||||
|
||||
**Beispiele:**
|
||||
```bash
|
||||
git commit -m "feat(auth): add login screen"
|
||||
git commit -m "fix(contracts): resolve date bug"
|
||||
git commit -m "docs(readme): update setup guide"
|
||||
```
|
||||
|
||||
### Fehlermeldungen
|
||||
|
||||
❌ **Falsch:**
|
||||
```bash
|
||||
git commit -m "added login"
|
||||
# Error: type must be lower-case
|
||||
```
|
||||
|
||||
✅ **Richtig:**
|
||||
```bash
|
||||
git commit -m "feat(auth): add login screen"
|
||||
```
|
||||
|
||||
## 🏷️ Version-Management
|
||||
|
||||
### Neue Version erstellen
|
||||
|
||||
```bash
|
||||
# Automatisches Versioning basierend auf Commits
|
||||
npm run release
|
||||
|
||||
# Spezifische Version
|
||||
npm run release:patch # 0.0.1 → 0.0.2
|
||||
npm run release:minor # 0.0.1 → 0.1.0
|
||||
npm run release:major # 0.0.1 → 1.0.0
|
||||
|
||||
# Pre-Releases
|
||||
npm run release:alpha # 0.0.1 → 0.0.2-alpha.0
|
||||
npm run release:beta # 0.0.1 → 0.0.2-beta.0
|
||||
npm run release:rc # 0.0.1 → 0.0.2-rc.0
|
||||
```
|
||||
|
||||
**Was passiert automatisch:**
|
||||
1. Version in `package.json` erhöhen
|
||||
2. `CHANGELOG.md` aktualisieren
|
||||
3. Git commit erstellen
|
||||
4. Git tag erstellen
|
||||
|
||||
### Nach Release
|
||||
|
||||
```bash
|
||||
git push --follow-tags origin main
|
||||
```
|
||||
|
||||
## 🌿 Branch-Workflow
|
||||
|
||||
### Feature entwickeln
|
||||
|
||||
```bash
|
||||
# 1. Branch erstellen
|
||||
git checkout develop
|
||||
git checkout -b feature/mein-feature
|
||||
|
||||
# 2. Entwickeln & committen
|
||||
git add .
|
||||
git commit -m "feat(module): beschreibung"
|
||||
|
||||
# 3. Push
|
||||
git push origin feature/mein-feature
|
||||
|
||||
# 4. Pull Request erstellen
|
||||
```
|
||||
|
||||
### Release durchführen
|
||||
|
||||
```bash
|
||||
# 1. Release-Branch
|
||||
git checkout develop
|
||||
git checkout -b release/v0.2.0
|
||||
|
||||
# 2. Version bumpen
|
||||
npm run release:minor
|
||||
|
||||
# 3. Merge zu main
|
||||
git checkout main
|
||||
git merge release/v0.2.0
|
||||
git push --follow-tags origin main
|
||||
|
||||
# 4. Merge zurück zu develop
|
||||
git checkout develop
|
||||
git merge release/v0.2.0
|
||||
git push origin develop
|
||||
```
|
||||
|
||||
## ✅ Pre-Commit Checks
|
||||
|
||||
Vor jedem Commit wird automatisch ausgeführt:
|
||||
1. **ESLint** - Code-Qualität prüfen
|
||||
2. **Prettier** - Code formatieren
|
||||
3. **Type-Check** - TypeScript-Fehler finden
|
||||
|
||||
Falls Fehler auftreten:
|
||||
```bash
|
||||
# Manuell fixen
|
||||
npm run lint:fix
|
||||
npm run format
|
||||
```
|
||||
|
||||
## 🎯 Checkliste vor erstem Commit
|
||||
|
||||
- [ ] Dependencies installiert (`npm install`)
|
||||
- [ ] Husky aktiviert (`npm run prepare`)
|
||||
- [ ] Git initialisiert (`git init`)
|
||||
- [ ] Remote hinzugefügt (`git remote add origin <url>`)
|
||||
- [ ] Commit-Format verstanden
|
||||
- [ ] Branch-Strategie verstanden
|
||||
|
||||
## 🔗 Nützliche Befehle
|
||||
|
||||
```bash
|
||||
# Version anzeigen
|
||||
npm version
|
||||
|
||||
# Changelog generieren
|
||||
npm run release
|
||||
|
||||
# Code formatieren
|
||||
npm run format
|
||||
|
||||
# Linting
|
||||
npm run lint:fix
|
||||
|
||||
# Type-Check
|
||||
npm run type-check
|
||||
|
||||
# Alle Checks
|
||||
npm run lint && npm run type-check && npm test
|
||||
```
|
||||
|
||||
## 📚 Weitere Dokumentation
|
||||
|
||||
- **[VERSIONING.md](./VERSIONING.md)** - Ausführliche Versioning-Strategie
|
||||
- **[GIT_WORKFLOW.md](./GIT_WORKFLOW.md)** - Detaillierter Git-Workflow
|
||||
- **[CHANGELOG.md](./CHANGELOG.md)** - Versions-Historie
|
||||
- **[CONTRIBUTING.md](./CONTRIBUTING.md)** - Contribution Guidelines (wird erstellt)
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Husky funktioniert nicht
|
||||
|
||||
```bash
|
||||
rm -rf .git/hooks
|
||||
npm run prepare
|
||||
```
|
||||
|
||||
### Commit wird abgelehnt
|
||||
|
||||
```bash
|
||||
# Commit-Message prüfen
|
||||
npx commitlint --edit
|
||||
|
||||
# Format: type(scope): subject
|
||||
# Beispiel: feat(auth): add login
|
||||
```
|
||||
|
||||
### Pre-Commit schlägt fehl
|
||||
|
||||
```bash
|
||||
# Dateien manuell fixen
|
||||
npm run lint:fix
|
||||
npm run format
|
||||
|
||||
# Nochmal committen
|
||||
git add .
|
||||
git commit -m "feat(module): beschreibung"
|
||||
```
|
||||
|
||||
## 🎉 Ready to Go!
|
||||
|
||||
Dein Projekt ist jetzt mit professioneller Versionsverwaltung ausgestattet:
|
||||
|
||||
✅ Automatische Commit-Validierung
|
||||
✅ Pre-Commit Hooks (Linting, Formatting)
|
||||
✅ Semantic Versioning
|
||||
✅ Automatisches Changelog
|
||||
✅ CI/CD-ready
|
||||
✅ Git-Flow Workflow
|
||||
|
||||
**Nächster Schritt:** Ersten Feature-Branch erstellen!
|
||||
|
||||
```bash
|
||||
git checkout -b feature/auth-screens
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
module.exports = {
|
||||
presets: ['module:@react-native/babel-preset'],
|
||||
plugins: [
|
||||
[
|
||||
'module-resolver',
|
||||
{
|
||||
root: ['./src'],
|
||||
extensions: ['.ios.js', '.android.js', '.js', '.ts', '.tsx', '.json'],
|
||||
alias: {
|
||||
'@modules': './src/modules',
|
||||
'@shared': './src/shared',
|
||||
'@navigation': './src/navigation',
|
||||
'@store': './src/store',
|
||||
'@config': './src/config',
|
||||
'@assets': './assets',
|
||||
},
|
||||
},
|
||||
],
|
||||
'react-native-reanimated/plugin',
|
||||
],
|
||||
};
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"name": "fristy",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "Modulare Vertrags-Management App für iOS und Android",
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios",
|
||||
"start": "react-native start",
|
||||
"test": "jest",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
|
||||
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "react-native clean",
|
||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
|
||||
"format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
|
||||
"release": "standard-version",
|
||||
"release:minor": "standard-version --release-as minor",
|
||||
"release:major": "standard-version --release-as major",
|
||||
"release:patch": "standard-version --release-as patch",
|
||||
"release:alpha": "standard-version --prerelease alpha",
|
||||
"release:beta": "standard-version --prerelease beta",
|
||||
"release:rc": "standard-version --prerelease rc",
|
||||
"prepare": "husky install",
|
||||
"version:bump": "npm version"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{json,md}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/native": "^6.1.9",
|
||||
"@react-navigation/native-stack": "^6.9.17",
|
||||
"@react-navigation/bottom-tabs": "^6.5.11",
|
||||
"@supabase/supabase-js": "^2.38.4",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.73.0",
|
||||
"react-native-safe-area-context": "^4.8.0",
|
||||
"react-native-screens": "^3.29.0",
|
||||
"react-native-vector-icons": "^10.0.3",
|
||||
"react-native-gesture-handler": "^2.14.0",
|
||||
"react-native-reanimated": "^3.6.1",
|
||||
"zustand": "^4.4.7",
|
||||
"date-fns": "^3.0.6",
|
||||
"react-native-document-picker": "^9.1.1",
|
||||
"react-native-push-notification": "^8.1.1",
|
||||
"react-native-encrypted-storage": "^4.0.3",
|
||||
"react-native-biometrics": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.5",
|
||||
"@babel/preset-env": "^7.23.5",
|
||||
"@babel/runtime": "^7.23.5",
|
||||
"@react-native/babel-preset": "^0.73.18",
|
||||
"@react-native/eslint-config": "^0.73.1",
|
||||
"@react-native/metro-config": "^0.73.2",
|
||||
"@react-native/typescript-config": "^0.73.1",
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-test-renderer": "^18.0.7",
|
||||
"typescript": "^5.3.3",
|
||||
"jest": "^29.7.0",
|
||||
"@testing-library/react-native": "^12.4.2",
|
||||
"prettier": "^3.1.1",
|
||||
"eslint": "^8.56.0",
|
||||
"@commitlint/cli": "^18.4.3",
|
||||
"@commitlint/config-conventional": "^18.4.3",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^15.2.0",
|
||||
"standard-version": "^9.5.0",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"semantic-release": "^22.0.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"npm": ">=9"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// Supabase Konfiguration
|
||||
export const SUPABASE_URL = process.env.SUPABASE_URL || 'https://your-project.supabase.co';
|
||||
export const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY || 'your-anon-key';
|
||||
|
||||
// API Endpoints (falls eigener Backend später)
|
||||
export const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:3000/api';
|
||||
|
||||
// App-Einstellungen
|
||||
export const APP_CONFIG = {
|
||||
enableBiometrics: true,
|
||||
enableNotifications: true,
|
||||
autoLogoutMinutes: 30,
|
||||
reminderDefaultDays: 30,
|
||||
};
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { createClient } from '@supabase/supabase-js';
|
||||
import { SUPABASE_URL, SUPABASE_ANON_KEY } from '@config';
|
||||
|
||||
export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
||||
auth: {
|
||||
autoRefreshToken: true,
|
||||
persistSession: true,
|
||||
detectSessionInUrl: false,
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
import { supabase } from '@config/supabase';
|
||||
import { useAuthStore } from '@store';
|
||||
|
||||
interface LoginCredentials {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface RegisterCredentials extends LoginCredentials {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const authService = {
|
||||
/**
|
||||
* Benutzer registrieren
|
||||
*/
|
||||
async register({ email, password, name }: RegisterCredentials) {
|
||||
try {
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
data: {
|
||||
name,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
return { data, error: null };
|
||||
} catch (error: any) {
|
||||
return { data: null, error: error.message };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Benutzer anmelden
|
||||
*/
|
||||
async login({ email, password }: LoginCredentials) {
|
||||
try {
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
// Store session
|
||||
if (data.session) {
|
||||
useAuthStore.getState().setSession(data.session);
|
||||
useAuthStore.getState().setUser({
|
||||
id: data.user!.id,
|
||||
email: data.user!.email!,
|
||||
name: data.user!.user_metadata?.name || '',
|
||||
createdAt: new Date(data.user!.created_at),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
return { data, error: null };
|
||||
} catch (error: any) {
|
||||
return { data: null, error: error.message };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Benutzer abmelden
|
||||
*/
|
||||
async logout() {
|
||||
try {
|
||||
const { error } = await supabase.auth.signOut();
|
||||
if (error) throw error;
|
||||
|
||||
useAuthStore.getState().logout();
|
||||
return { error: null };
|
||||
} catch (error: any) {
|
||||
return { error: error.message };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Passwort zurücksetzen
|
||||
*/
|
||||
async resetPassword(email: string) {
|
||||
try {
|
||||
const { error } = await supabase.auth.resetPasswordForEmail(email);
|
||||
if (error) throw error;
|
||||
return { error: null };
|
||||
} catch (error: any) {
|
||||
return { error: error.message };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Session prüfen und wiederherstellen
|
||||
*/
|
||||
async restoreSession() {
|
||||
try {
|
||||
const { data, error } = await supabase.auth.getSession();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
if (data.session) {
|
||||
useAuthStore.getState().setSession(data.session);
|
||||
useAuthStore.getState().setUser({
|
||||
id: data.session.user.id,
|
||||
email: data.session.user.email!,
|
||||
name: data.session.user.user_metadata?.name || '',
|
||||
createdAt: new Date(data.session.user.created_at),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
return { session: data.session, error: null };
|
||||
} catch (error: any) {
|
||||
return { session: null, error: error.message };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Auth State Listener
|
||||
*/
|
||||
onAuthStateChange(callback: (session: any) => void) {
|
||||
return supabase.auth.onAuthStateChange((_event, session) => {
|
||||
callback(session);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
import { supabase } from '@config/supabase';
|
||||
import { Contract } from '@shared/types';
|
||||
import { useContractStore } from '@store';
|
||||
|
||||
export const contractService = {
|
||||
/**
|
||||
* Alle Verträge eines Benutzers laden
|
||||
*/
|
||||
async fetchContracts(userId: string) {
|
||||
try {
|
||||
useContractStore.getState().setLoading(true);
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('contracts')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
const contracts: Contract[] = (data || []).map((item: any) => ({
|
||||
id: item.id,
|
||||
userId: item.user_id,
|
||||
name: item.name,
|
||||
provider: item.provider,
|
||||
category: item.category,
|
||||
startDate: new Date(item.start_date),
|
||||
endDate: item.end_date ? new Date(item.end_date) : undefined,
|
||||
cancellationDeadline: item.cancellation_deadline
|
||||
? new Date(item.cancellation_deadline)
|
||||
: undefined,
|
||||
noticePeriod: item.notice_period,
|
||||
cost: item.cost,
|
||||
billingCycle: item.billing_cycle,
|
||||
autoRenewal: item.auto_renewal,
|
||||
notes: item.notes,
|
||||
documents: item.documents || [],
|
||||
reminderEnabled: item.reminder_enabled,
|
||||
reminderDays: item.reminder_days,
|
||||
status: item.status,
|
||||
createdAt: new Date(item.created_at),
|
||||
updatedAt: new Date(item.updated_at),
|
||||
}));
|
||||
|
||||
useContractStore.getState().setContracts(contracts);
|
||||
useContractStore.getState().setLoading(false);
|
||||
|
||||
return { data: contracts, error: null };
|
||||
} catch (error: any) {
|
||||
useContractStore.getState().setLoading(false);
|
||||
useContractStore.getState().setError(error.message);
|
||||
return { data: null, error: error.message };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Neuen Vertrag erstellen
|
||||
*/
|
||||
async createContract(contract: Omit<Contract, 'id' | 'createdAt' | 'updatedAt'>) {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('contracts')
|
||||
.insert([
|
||||
{
|
||||
user_id: contract.userId,
|
||||
name: contract.name,
|
||||
provider: contract.provider,
|
||||
category: contract.category,
|
||||
start_date: contract.startDate.toISOString(),
|
||||
end_date: contract.endDate?.toISOString(),
|
||||
cancellation_deadline: contract.cancellationDeadline?.toISOString(),
|
||||
notice_period: contract.noticePeriod,
|
||||
cost: contract.cost,
|
||||
billing_cycle: contract.billingCycle,
|
||||
auto_renewal: contract.autoRenewal,
|
||||
notes: contract.notes,
|
||||
documents: contract.documents,
|
||||
reminder_enabled: contract.reminderEnabled,
|
||||
reminder_days: contract.reminderDays,
|
||||
status: contract.status,
|
||||
},
|
||||
])
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
const newContract: Contract = {
|
||||
id: data.id,
|
||||
userId: data.user_id,
|
||||
name: data.name,
|
||||
provider: data.provider,
|
||||
category: data.category,
|
||||
startDate: new Date(data.start_date),
|
||||
endDate: data.end_date ? new Date(data.end_date) : undefined,
|
||||
cancellationDeadline: data.cancellation_deadline
|
||||
? new Date(data.cancellation_deadline)
|
||||
: undefined,
|
||||
noticePeriod: data.notice_period,
|
||||
cost: data.cost,
|
||||
billingCycle: data.billing_cycle,
|
||||
autoRenewal: data.auto_renewal,
|
||||
notes: data.notes,
|
||||
documents: data.documents || [],
|
||||
reminderEnabled: data.reminder_enabled,
|
||||
reminderDays: data.reminder_days,
|
||||
status: data.status,
|
||||
createdAt: new Date(data.created_at),
|
||||
updatedAt: new Date(data.updated_at),
|
||||
};
|
||||
|
||||
useContractStore.getState().addContract(newContract);
|
||||
|
||||
return { data: newContract, error: null };
|
||||
} catch (error: any) {
|
||||
return { data: null, error: error.message };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Vertrag aktualisieren
|
||||
*/
|
||||
async updateContract(id: string, updates: Partial<Contract>) {
|
||||
try {
|
||||
const dbUpdates: any = {};
|
||||
|
||||
if (updates.name) dbUpdates.name = updates.name;
|
||||
if (updates.provider) dbUpdates.provider = updates.provider;
|
||||
if (updates.category) dbUpdates.category = updates.category;
|
||||
if (updates.startDate) dbUpdates.start_date = updates.startDate.toISOString();
|
||||
if (updates.endDate) dbUpdates.end_date = updates.endDate.toISOString();
|
||||
if (updates.cancellationDeadline)
|
||||
dbUpdates.cancellation_deadline = updates.cancellationDeadline.toISOString();
|
||||
if (updates.noticePeriod !== undefined)
|
||||
dbUpdates.notice_period = updates.noticePeriod;
|
||||
if (updates.cost !== undefined) dbUpdates.cost = updates.cost;
|
||||
if (updates.billingCycle) dbUpdates.billing_cycle = updates.billingCycle;
|
||||
if (updates.autoRenewal !== undefined)
|
||||
dbUpdates.auto_renewal = updates.autoRenewal;
|
||||
if (updates.notes !== undefined) dbUpdates.notes = updates.notes;
|
||||
if (updates.documents) dbUpdates.documents = updates.documents;
|
||||
if (updates.reminderEnabled !== undefined)
|
||||
dbUpdates.reminder_enabled = updates.reminderEnabled;
|
||||
if (updates.reminderDays !== undefined)
|
||||
dbUpdates.reminder_days = updates.reminderDays;
|
||||
if (updates.status) dbUpdates.status = updates.status;
|
||||
|
||||
dbUpdates.updated_at = new Date().toISOString();
|
||||
|
||||
const { error } = await supabase
|
||||
.from('contracts')
|
||||
.update(dbUpdates)
|
||||
.eq('id', id);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
useContractStore.getState().updateContract(id, updates);
|
||||
|
||||
return { error: null };
|
||||
} catch (error: any) {
|
||||
return { error: error.message };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Vertrag löschen
|
||||
*/
|
||||
async deleteContract(id: string) {
|
||||
try {
|
||||
const { error } = await supabase.from('contracts').delete().eq('id', id);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
useContractStore.getState().deleteContract(id);
|
||||
|
||||
return { error: null };
|
||||
} catch (error: any) {
|
||||
return { error: error.message };
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
import React from 'react';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { useAuthStore } from '@store';
|
||||
|
||||
// Auth Screens (werden später erstellt)
|
||||
// import { LoginScreen } from '@modules/auth/screens/LoginScreen';
|
||||
// import { RegisterScreen } from '@modules/auth/screens/RegisterScreen';
|
||||
|
||||
// App Screens (werden später erstellt)
|
||||
// import { DashboardScreen } from '@modules/dashboard/screens/DashboardScreen';
|
||||
// import { ContractsScreen } from '@modules/contracts/screens/ContractsScreen';
|
||||
// import { AddContractScreen } from '@modules/contracts/screens/AddContractScreen';
|
||||
// import { NotificationsScreen } from '@modules/notifications/screens/NotificationsScreen';
|
||||
// import { ProfileScreen } from '@modules/profile/screens/ProfileScreen';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
const Tab = createBottomTabNavigator();
|
||||
|
||||
// Placeholder Komponenten für die Navigation
|
||||
const PlaceholderScreen = ({ title }: { title: string }) => (
|
||||
<div style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<h1>{title}</h1>
|
||||
</div>
|
||||
);
|
||||
|
||||
const LoginScreen = () => <PlaceholderScreen title="Login" />;
|
||||
const RegisterScreen = () => <PlaceholderScreen title="Register" />;
|
||||
const DashboardScreen = () => <PlaceholderScreen title="Dashboard" />;
|
||||
const ContractsScreen = () => <PlaceholderScreen title="Contracts" />;
|
||||
const AddContractScreen = () => <PlaceholderScreen title="Add Contract" />;
|
||||
const NotificationsScreen = () => <PlaceholderScreen title="Notifications" />;
|
||||
const ProfileScreen = () => <PlaceholderScreen title="Profile" />;
|
||||
|
||||
/**
|
||||
* Auth Stack - für nicht eingeloggte Benutzer
|
||||
*/
|
||||
const AuthStack = () => {
|
||||
return (
|
||||
<Stack.Navigator
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
}}>
|
||||
<Stack.Screen name="Login" component={LoginScreen} />
|
||||
<Stack.Screen name="Register" component={RegisterScreen} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tab Navigator - Hauptnavigation für eingeloggte Benutzer
|
||||
*/
|
||||
const AppTabs = () => {
|
||||
return (
|
||||
<Tab.Navigator
|
||||
screenOptions={{
|
||||
headerShown: true,
|
||||
tabBarActiveTintColor: '#6366F1',
|
||||
tabBarInactiveTintColor: '#9CA3AF',
|
||||
}}>
|
||||
<Tab.Screen
|
||||
name="Dashboard"
|
||||
component={DashboardScreen}
|
||||
options={{
|
||||
title: 'Übersicht',
|
||||
// tabBarIcon: ({ color, size }) => (
|
||||
// <Icon name="home" size={size} color={color} />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="Contracts"
|
||||
component={ContractsScreen}
|
||||
options={{
|
||||
title: 'Verträge',
|
||||
// tabBarIcon: ({ color, size }) => (
|
||||
// <Icon name="file-text" size={size} color={color} />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="AddContract"
|
||||
component={AddContractScreen}
|
||||
options={{
|
||||
title: 'Hinzufügen',
|
||||
// tabBarIcon: ({ color, size }) => (
|
||||
// <Icon name="plus-circle" size={size} color={color} />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="Notifications"
|
||||
component={NotificationsScreen}
|
||||
options={{
|
||||
title: 'Erinnerungen',
|
||||
// tabBarIcon: ({ color, size }) => (
|
||||
// <Icon name="bell" size={size} color={color} />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="Profile"
|
||||
component={ProfileScreen}
|
||||
options={{
|
||||
title: 'Profil',
|
||||
// tabBarIcon: ({ color, size }) => (
|
||||
// <Icon name="user" size={size} color={color} />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* App Stack - für eingeloggte Benutzer
|
||||
*/
|
||||
const AppStack = () => {
|
||||
return (
|
||||
<Stack.Navigator>
|
||||
<Stack.Screen
|
||||
name="MainTabs"
|
||||
component={AppTabs}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Root Navigation
|
||||
*/
|
||||
export const RootNavigation = () => {
|
||||
const { user } = useAuthStore();
|
||||
|
||||
return (
|
||||
<NavigationContainer>
|
||||
{user ? <AppStack /> : <AuthStack />}
|
||||
</NavigationContainer>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { RootNavigation } from './RootNavigation';
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
TouchableOpacity,
|
||||
Text,
|
||||
ActivityIndicator,
|
||||
StyleSheet,
|
||||
ViewStyle,
|
||||
TextStyle,
|
||||
} from 'react-native';
|
||||
import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '@shared/theme';
|
||||
|
||||
interface ButtonProps {
|
||||
title: string;
|
||||
onPress: () => void;
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'danger';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
fullWidth?: boolean;
|
||||
style?: ViewStyle;
|
||||
textStyle?: TextStyle;
|
||||
}
|
||||
|
||||
export const Button: React.FC<ButtonProps> = ({
|
||||
title,
|
||||
onPress,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
loading = false,
|
||||
disabled = false,
|
||||
fullWidth = false,
|
||||
style,
|
||||
textStyle,
|
||||
}) => {
|
||||
const getButtonStyle = (): ViewStyle => {
|
||||
const baseStyle: ViewStyle = {
|
||||
...styles.button,
|
||||
...styles[`button_${size}`],
|
||||
...(fullWidth && styles.fullWidth),
|
||||
};
|
||||
|
||||
if (disabled || loading) {
|
||||
return { ...baseStyle, ...styles.disabled };
|
||||
}
|
||||
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
return { ...baseStyle, backgroundColor: COLORS.primary };
|
||||
case 'secondary':
|
||||
return { ...baseStyle, backgroundColor: COLORS.secondary };
|
||||
case 'outline':
|
||||
return {
|
||||
...baseStyle,
|
||||
backgroundColor: 'transparent',
|
||||
borderWidth: 1,
|
||||
borderColor: COLORS.primary,
|
||||
};
|
||||
case 'danger':
|
||||
return { ...baseStyle, backgroundColor: COLORS.danger };
|
||||
default:
|
||||
return baseStyle;
|
||||
}
|
||||
};
|
||||
|
||||
const getTextStyle = (): TextStyle => {
|
||||
const baseTextStyle: TextStyle = {
|
||||
...styles.text,
|
||||
...styles[`text_${size}`],
|
||||
};
|
||||
|
||||
if (variant === 'outline') {
|
||||
return { ...baseTextStyle, color: COLORS.primary };
|
||||
}
|
||||
|
||||
return baseTextStyle;
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[getButtonStyle(), style]}
|
||||
onPress={onPress}
|
||||
disabled={disabled || loading}
|
||||
activeOpacity={0.7}>
|
||||
{loading ? (
|
||||
<ActivityIndicator color="#FFF" size="small" />
|
||||
) : (
|
||||
<Text style={[getTextStyle(), textStyle]}>{title}</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: BORDER_RADIUS.md,
|
||||
...SHADOWS.sm,
|
||||
},
|
||||
button_sm: {
|
||||
paddingVertical: SPACING.sm,
|
||||
paddingHorizontal: SPACING.md,
|
||||
},
|
||||
button_md: {
|
||||
paddingVertical: SPACING.md,
|
||||
paddingHorizontal: SPACING.lg,
|
||||
},
|
||||
button_lg: {
|
||||
paddingVertical: SPACING.lg,
|
||||
paddingHorizontal: SPACING.xl,
|
||||
},
|
||||
text: {
|
||||
color: '#FFF',
|
||||
fontWeight: '600',
|
||||
},
|
||||
text_sm: {
|
||||
fontSize: FONT_SIZE.sm,
|
||||
},
|
||||
text_md: {
|
||||
fontSize: FONT_SIZE.md,
|
||||
},
|
||||
text_lg: {
|
||||
fontSize: FONT_SIZE.lg,
|
||||
},
|
||||
fullWidth: {
|
||||
width: '100%',
|
||||
},
|
||||
disabled: {
|
||||
backgroundColor: COLORS.border,
|
||||
opacity: 0.6,
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
import { Contract, ContractCategory } from '@shared/types';
|
||||
import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '@shared/theme';
|
||||
import { format } from 'date-fns';
|
||||
import { de } from 'date-fns/locale';
|
||||
|
||||
interface ContractCardProps {
|
||||
contract: Contract;
|
||||
onPress: () => void;
|
||||
style?: ViewStyle;
|
||||
}
|
||||
|
||||
export const ContractCard: React.FC<ContractCardProps> = ({
|
||||
contract,
|
||||
onPress,
|
||||
style,
|
||||
}) => {
|
||||
const getCategoryColor = (category: ContractCategory): string => {
|
||||
return COLORS.categories[category] || COLORS.textSecondary;
|
||||
};
|
||||
|
||||
const formatCost = (cost: number, cycle: string): string => {
|
||||
const formatted = cost.toFixed(2);
|
||||
switch (cycle) {
|
||||
case 'monthly':
|
||||
return `${formatted}€/Monat`;
|
||||
case 'quarterly':
|
||||
return `${formatted}€/Quartal`;
|
||||
case 'yearly':
|
||||
return `${formatted}€/Jahr`;
|
||||
default:
|
||||
return `${formatted}€`;
|
||||
}
|
||||
};
|
||||
|
||||
const getDaysUntilDeadline = (): number | null => {
|
||||
if (!contract.cancellationDeadline) return null;
|
||||
const today = new Date();
|
||||
const deadline = new Date(contract.cancellationDeadline);
|
||||
const diff = deadline.getTime() - today.getTime();
|
||||
return Math.ceil(diff / (1000 * 60 * 60 * 24));
|
||||
};
|
||||
|
||||
const daysLeft = getDaysUntilDeadline();
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.card, style]}
|
||||
onPress={onPress}
|
||||
activeOpacity={0.7}>
|
||||
<View
|
||||
style={[
|
||||
styles.categoryIndicator,
|
||||
{ backgroundColor: getCategoryColor(contract.category) },
|
||||
]}
|
||||
/>
|
||||
|
||||
<View style={styles.content}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.name} numberOfLines={1}>
|
||||
{contract.name}
|
||||
</Text>
|
||||
<Text style={styles.cost}>
|
||||
{formatCost(contract.cost, contract.billingCycle)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.provider} numberOfLines={1}>
|
||||
{contract.provider}
|
||||
</Text>
|
||||
|
||||
{contract.cancellationDeadline && (
|
||||
<View style={styles.deadline}>
|
||||
<Text
|
||||
style={[
|
||||
styles.deadlineText,
|
||||
daysLeft && daysLeft < 30 && styles.deadlineWarning,
|
||||
daysLeft && daysLeft < 7 && styles.deadlineDanger,
|
||||
]}>
|
||||
Kündigung bis:{' '}
|
||||
{format(new Date(contract.cancellationDeadline), 'dd.MM.yyyy', {
|
||||
locale: de,
|
||||
})}
|
||||
</Text>
|
||||
{daysLeft !== null && daysLeft > 0 && (
|
||||
<Text
|
||||
style={[
|
||||
styles.daysLeft,
|
||||
daysLeft < 30 && styles.daysLeftWarning,
|
||||
daysLeft < 7 && styles.daysLeftDanger,
|
||||
]}>
|
||||
{daysLeft} Tage
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{contract.status === 'cancelled' && (
|
||||
<View style={styles.statusBadge}>
|
||||
<Text style={styles.statusText}>Gekündigt</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
backgroundColor: COLORS.surface,
|
||||
borderRadius: BORDER_RADIUS.lg,
|
||||
marginBottom: SPACING.md,
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
...SHADOWS.md,
|
||||
},
|
||||
categoryIndicator: {
|
||||
width: 6,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
padding: SPACING.md,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: SPACING.xs,
|
||||
},
|
||||
name: {
|
||||
flex: 1,
|
||||
fontSize: FONT_SIZE.lg,
|
||||
fontWeight: '600',
|
||||
color: COLORS.text,
|
||||
marginRight: SPACING.sm,
|
||||
},
|
||||
cost: {
|
||||
fontSize: FONT_SIZE.md,
|
||||
fontWeight: '700',
|
||||
color: COLORS.primary,
|
||||
},
|
||||
provider: {
|
||||
fontSize: FONT_SIZE.sm,
|
||||
color: COLORS.textSecondary,
|
||||
marginBottom: SPACING.sm,
|
||||
},
|
||||
deadline: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
deadlineText: {
|
||||
fontSize: FONT_SIZE.sm,
|
||||
color: COLORS.textSecondary,
|
||||
},
|
||||
deadlineWarning: {
|
||||
color: COLORS.warning,
|
||||
},
|
||||
deadlineDanger: {
|
||||
color: COLORS.danger,
|
||||
fontWeight: '600',
|
||||
},
|
||||
daysLeft: {
|
||||
fontSize: FONT_SIZE.xs,
|
||||
color: COLORS.textLight,
|
||||
fontWeight: '500',
|
||||
},
|
||||
daysLeftWarning: {
|
||||
color: COLORS.warning,
|
||||
},
|
||||
daysLeftDanger: {
|
||||
color: COLORS.danger,
|
||||
fontWeight: '700',
|
||||
},
|
||||
statusBadge: {
|
||||
marginTop: SPACING.sm,
|
||||
alignSelf: 'flex-start',
|
||||
backgroundColor: COLORS.danger,
|
||||
paddingHorizontal: SPACING.sm,
|
||||
paddingVertical: 4,
|
||||
borderRadius: BORDER_RADIUS.sm,
|
||||
},
|
||||
statusText: {
|
||||
fontSize: FONT_SIZE.xs,
|
||||
color: '#FFF',
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react';
|
||||
import { TextInput, Text, View, StyleSheet, TextInputProps } from 'react-native';
|
||||
import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS } from '@shared/theme';
|
||||
|
||||
interface InputProps extends TextInputProps {
|
||||
label?: string;
|
||||
error?: string;
|
||||
helperText?: string;
|
||||
}
|
||||
|
||||
export const Input: React.FC<InputProps> = ({
|
||||
label,
|
||||
error,
|
||||
helperText,
|
||||
style,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{label && <Text style={styles.label}>{label}</Text>}
|
||||
<TextInput
|
||||
style={[
|
||||
styles.input,
|
||||
error && styles.inputError,
|
||||
style,
|
||||
]}
|
||||
placeholderTextColor={COLORS.textLight}
|
||||
{...props}
|
||||
/>
|
||||
{error && <Text style={styles.errorText}>{error}</Text>}
|
||||
{helperText && !error && (
|
||||
<Text style={styles.helperText}>{helperText}</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginBottom: SPACING.md,
|
||||
},
|
||||
label: {
|
||||
fontSize: FONT_SIZE.sm,
|
||||
fontWeight: '600',
|
||||
color: COLORS.text,
|
||||
marginBottom: SPACING.xs,
|
||||
},
|
||||
input: {
|
||||
backgroundColor: COLORS.surface,
|
||||
borderWidth: 1,
|
||||
borderColor: COLORS.border,
|
||||
borderRadius: BORDER_RADIUS.md,
|
||||
paddingHorizontal: SPACING.md,
|
||||
paddingVertical: SPACING.sm,
|
||||
fontSize: FONT_SIZE.md,
|
||||
color: COLORS.text,
|
||||
},
|
||||
inputError: {
|
||||
borderColor: COLORS.danger,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: FONT_SIZE.xs,
|
||||
color: COLORS.danger,
|
||||
marginTop: SPACING.xs,
|
||||
},
|
||||
helperText: {
|
||||
fontSize: FONT_SIZE.xs,
|
||||
color: COLORS.textSecondary,
|
||||
marginTop: SPACING.xs,
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export { Button } from './Button';
|
||||
export { ContractCard } from './ContractCard';
|
||||
export { Input } from './Input';
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { ContractCategory } from '../types';
|
||||
|
||||
export const CONTRACT_CATEGORIES = [
|
||||
{ value: ContractCategory.MOBILE, label: 'Mobilfunk' },
|
||||
{ value: ContractCategory.INTERNET, label: 'Internet & DSL' },
|
||||
{ value: ContractCategory.STREAMING, label: 'Streaming' },
|
||||
{ value: ContractCategory.INSURANCE, label: 'Versicherungen' },
|
||||
{ value: ContractCategory.UTILITIES, label: 'Strom, Gas & Wasser' },
|
||||
{ value: ContractCategory.GYM, label: 'Fitnessstudio' },
|
||||
{ value: ContractCategory.SUBSCRIPTIONS, label: 'Abonnements' },
|
||||
{ value: ContractCategory.OTHER, label: 'Sonstiges' },
|
||||
];
|
||||
|
||||
export const BILLING_CYCLES = [
|
||||
{ value: 'monthly', label: 'Monatlich' },
|
||||
{ value: 'quarterly', label: 'Vierteljährlich' },
|
||||
{ value: 'yearly', label: 'Jährlich' },
|
||||
];
|
||||
|
||||
export const NOTICE_PERIODS = [
|
||||
{ value: 1, label: '1 Monat' },
|
||||
{ value: 2, label: '2 Monate' },
|
||||
{ value: 3, label: '3 Monate' },
|
||||
{ value: 6, label: '6 Monate' },
|
||||
{ value: 12, label: '12 Monate' },
|
||||
];
|
||||
|
||||
export const REMINDER_DAYS = [
|
||||
{ value: 7, label: '1 Woche vorher' },
|
||||
{ value: 14, label: '2 Wochen vorher' },
|
||||
{ value: 30, label: '1 Monat vorher' },
|
||||
{ value: 60, label: '2 Monate vorher' },
|
||||
{ value: 90, label: '3 Monate vorher' },
|
||||
];
|
||||
|
||||
export const APP_NAME = 'Fristy';
|
||||
export const APP_VERSION = '0.0.1';
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// Design System Konstanten
|
||||
export const COLORS = {
|
||||
// Primary
|
||||
primary: '#6366F1',
|
||||
primaryDark: '#4F46E5',
|
||||
primaryLight: '#818CF8',
|
||||
|
||||
// Secondary
|
||||
secondary: '#10B981',
|
||||
secondaryDark: '#059669',
|
||||
secondaryLight: '#34D399',
|
||||
|
||||
// Status
|
||||
warning: '#F59E0B',
|
||||
danger: '#EF4444',
|
||||
success: '#10B981',
|
||||
info: '#3B82F6',
|
||||
|
||||
// Neutrals
|
||||
background: '#F9FAFB',
|
||||
surface: '#FFFFFF',
|
||||
border: '#E5E7EB',
|
||||
text: '#111827',
|
||||
textSecondary: '#6B7280',
|
||||
textLight: '#9CA3AF',
|
||||
|
||||
// Kategorien
|
||||
categories: {
|
||||
mobile: '#3B82F6',
|
||||
internet: '#8B5CF6',
|
||||
streaming: '#EC4899',
|
||||
insurance: '#10B981',
|
||||
utilities: '#F59E0B',
|
||||
gym: '#EF4444',
|
||||
subscriptions: '#6366F1',
|
||||
other: '#6B7280',
|
||||
},
|
||||
};
|
||||
|
||||
export const SPACING = {
|
||||
xs: 4,
|
||||
sm: 8,
|
||||
md: 16,
|
||||
lg: 24,
|
||||
xl: 32,
|
||||
xxl: 48,
|
||||
};
|
||||
|
||||
export const FONT_SIZE = {
|
||||
xs: 12,
|
||||
sm: 14,
|
||||
md: 16,
|
||||
lg: 18,
|
||||
xl: 20,
|
||||
xxl: 24,
|
||||
xxxl: 32,
|
||||
};
|
||||
|
||||
export const FONT_WEIGHT = {
|
||||
regular: '400' as const,
|
||||
medium: '500' as const,
|
||||
semibold: '600' as const,
|
||||
bold: '700' as const,
|
||||
};
|
||||
|
||||
export const BORDER_RADIUS = {
|
||||
sm: 4,
|
||||
md: 8,
|
||||
lg: 12,
|
||||
xl: 16,
|
||||
full: 9999,
|
||||
};
|
||||
|
||||
export const SHADOWS = {
|
||||
sm: {
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 2,
|
||||
elevation: 1,
|
||||
},
|
||||
md: {
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
lg: {
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 8,
|
||||
elevation: 5,
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// TypeScript Typen für die gesamte App
|
||||
export enum ContractCategory {
|
||||
MOBILE = 'mobile',
|
||||
INTERNET = 'internet',
|
||||
STREAMING = 'streaming',
|
||||
INSURANCE = 'insurance',
|
||||
UTILITIES = 'utilities',
|
||||
GYM = 'gym',
|
||||
SUBSCRIPTIONS = 'subscriptions',
|
||||
OTHER = 'other',
|
||||
}
|
||||
|
||||
export type BillingCycle = 'monthly' | 'quarterly' | 'yearly';
|
||||
export type ContractStatus = 'active' | 'cancelled' | 'expired';
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface Document {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
size: number;
|
||||
type: string;
|
||||
uploadedAt: Date;
|
||||
}
|
||||
|
||||
export interface Contract {
|
||||
id: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
provider: string;
|
||||
category: ContractCategory;
|
||||
startDate: Date;
|
||||
endDate?: Date;
|
||||
cancellationDeadline?: Date;
|
||||
noticePeriod: number; // in Monaten
|
||||
cost: number;
|
||||
billingCycle: BillingCycle;
|
||||
autoRenewal: boolean;
|
||||
notes?: string;
|
||||
documents: Document[];
|
||||
reminderEnabled: boolean;
|
||||
reminderDays: number;
|
||||
status: ContractStatus;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
id: string;
|
||||
userId: string;
|
||||
contractId: string;
|
||||
title: string;
|
||||
message: string;
|
||||
type: 'cancellation' | 'payment' | 'renewal' | 'custom';
|
||||
scheduledFor: Date;
|
||||
sent: boolean;
|
||||
read: boolean;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface ContractStats {
|
||||
totalContracts: number;
|
||||
totalMonthlyCost: number;
|
||||
totalYearlyCost: number;
|
||||
upcomingDeadlines: number;
|
||||
contractsByCategory: Record<ContractCategory, number>;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { create } from 'zustand';
|
||||
import { User } from '@shared/types';
|
||||
|
||||
interface AuthStore {
|
||||
user: User | null;
|
||||
session: any | null;
|
||||
loading: boolean;
|
||||
|
||||
setUser: (user: User | null) => void;
|
||||
setSession: (session: any | null) => void;
|
||||
setLoading: (loading: boolean) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthStore>((set) => ({
|
||||
user: null,
|
||||
session: null,
|
||||
loading: true,
|
||||
|
||||
setUser: (user) => set({ user }),
|
||||
setSession: (session) => set({ session }),
|
||||
setLoading: (loading) => set({ loading }),
|
||||
|
||||
logout: () =>
|
||||
set({
|
||||
user: null,
|
||||
session: null,
|
||||
}),
|
||||
}));
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import { create } from 'zustand';
|
||||
import { Contract, ContractCategory } from '@shared/types';
|
||||
|
||||
interface ContractStore {
|
||||
contracts: Contract[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
|
||||
// Actions
|
||||
setContracts: (contracts: Contract[]) => void;
|
||||
addContract: (contract: Contract) => void;
|
||||
updateContract: (id: string, data: Partial<Contract>) => void;
|
||||
deleteContract: (id: string) => void;
|
||||
|
||||
// Selectors
|
||||
getContractById: (id: string) => Contract | undefined;
|
||||
getContractsByCategory: (category: ContractCategory) => Contract[];
|
||||
getActiveContracts: () => Contract[];
|
||||
getTotalMonthlyCost: () => number;
|
||||
getUpcomingDeadlines: (days: number) => Contract[];
|
||||
|
||||
// Loading states
|
||||
setLoading: (loading: boolean) => void;
|
||||
setError: (error: string | null) => void;
|
||||
}
|
||||
|
||||
export const useContractStore = create<ContractStore>((set, get) => ({
|
||||
contracts: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
|
||||
setContracts: (contracts) => set({ contracts }),
|
||||
|
||||
addContract: (contract) =>
|
||||
set((state) => ({
|
||||
contracts: [...state.contracts, contract],
|
||||
})),
|
||||
|
||||
updateContract: (id, data) =>
|
||||
set((state) => ({
|
||||
contracts: state.contracts.map((contract) =>
|
||||
contract.id === id ? { ...contract, ...data } : contract
|
||||
),
|
||||
})),
|
||||
|
||||
deleteContract: (id) =>
|
||||
set((state) => ({
|
||||
contracts: state.contracts.filter((contract) => contract.id !== id),
|
||||
})),
|
||||
|
||||
getContractById: (id) => {
|
||||
return get().contracts.find((contract) => contract.id === id);
|
||||
},
|
||||
|
||||
getContractsByCategory: (category) => {
|
||||
return get().contracts.filter(
|
||||
(contract) => contract.category === category
|
||||
);
|
||||
},
|
||||
|
||||
getActiveContracts: () => {
|
||||
return get().contracts.filter(
|
||||
(contract) => contract.status === 'active'
|
||||
);
|
||||
},
|
||||
|
||||
getTotalMonthlyCost: () => {
|
||||
return get()
|
||||
.contracts.filter((c) => c.status === 'active')
|
||||
.reduce((total, contract) => {
|
||||
let monthlyCost = contract.cost;
|
||||
if (contract.billingCycle === 'yearly') {
|
||||
monthlyCost = contract.cost / 12;
|
||||
} else if (contract.billingCycle === 'quarterly') {
|
||||
monthlyCost = contract.cost / 3;
|
||||
}
|
||||
return total + monthlyCost;
|
||||
}, 0);
|
||||
},
|
||||
|
||||
getUpcomingDeadlines: (days) => {
|
||||
const now = new Date();
|
||||
const futureDate = new Date();
|
||||
futureDate.setDate(futureDate.getDate() + days);
|
||||
|
||||
return get()
|
||||
.contracts.filter((contract) => {
|
||||
if (!contract.cancellationDeadline || contract.status !== 'active') {
|
||||
return false;
|
||||
}
|
||||
const deadline = new Date(contract.cancellationDeadline);
|
||||
return deadline >= now && deadline <= futureDate;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const dateA = new Date(a.cancellationDeadline!);
|
||||
const dateB = new Date(b.cancellationDeadline!);
|
||||
return dateA.getTime() - dateB.getTime();
|
||||
});
|
||||
},
|
||||
|
||||
setLoading: (loading) => set({ loading }),
|
||||
setError: (error) => set({ error }),
|
||||
}));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { useContractStore } from './contractStore';
|
||||
export { useAuthStore } from './authStore';
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"extends": "@react-native/typescript-config/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"lib": ["es2019"],
|
||||
"allowJs": true,
|
||||
"jsx": "react-native",
|
||||
"noEmit": true,
|
||||
"isolatedModules": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@modules/*": ["src/modules/*"],
|
||||
"@shared/*": ["src/shared/*"],
|
||||
"@navigation/*": ["src/navigation/*"],
|
||||
"@store/*": ["src/store/*"],
|
||||
"@config/*": ["src/config/*"],
|
||||
"@assets/*": ["assets/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*", "App.tsx"],
|
||||
"exclude": ["node_modules", "babel.config.js", "metro.config.js", "jest.config.js"]
|
||||
}
|
||||
Loading…
Reference in New Issue