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