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:
Justn Ursan 2025-11-17 22:33:59 +01:00
commit 4d48142752
39 changed files with 3638 additions and 0 deletions

28
.commitlintrc.js Normal file
View File

@ -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],
},
};

14
.env.example Normal file
View File

@ -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

16
.eslintrc.js Normal file
View File

@ -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',
},
},
],
};

94
.github/workflows/ci.yml vendored Normal file
View File

@ -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

46
.github/workflows/release.yml vendored Normal file
View File

@ -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

72
.gitignore vendored Normal file
View File

@ -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

4
.husky/commit-msg Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit ${1}

4
.husky/pre-commit Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run lint-staged

10
.prettierrc Normal file
View File

@ -0,0 +1,10 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 100,
"arrowParens": "avoid",
"bracketSpacing": true,
"endOfLine": "lf"
}

9
.prettierrc.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
arrowParens: 'avoid',
bracketSameLine: true,
bracketSpacing: true,
singleQuote: true,
trailingComma: 'es5',
tabWidth: 2,
semi: true,
};

22
.releaserc.js Normal file
View File

@ -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',
],
};

254
ARCHITECTURE.md Normal file
View File

@ -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

44
App.tsx Normal file
View File

@ -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;

40
CHANGELOG.md Normal file
View File

@ -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

300
GETTING_STARTED.md Normal file
View File

@ -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

248
GITEA_SETUP.md Normal file
View File

@ -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
```

423
GIT_WORKFLOW.md Normal file
View File

@ -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>
```

79
README.md Normal file
View File

@ -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

337
VERSIONING.md Normal file
View File

@ -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

242
VERSIONING_SETUP.md Normal file
View File

@ -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
```

21
babel.config.js Normal file
View File

@ -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',
],
};

83
package.json Normal file
View File

@ -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"
}
}

14
src/config/index.ts Normal file
View File

@ -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,
};

10
src/config/supabase.ts Normal file
View File

@ -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,
},
});

View File

@ -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);
});
},
};

View File

@ -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 };
}
},
};

View File

@ -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>
);
};

1
src/navigation/index.ts Normal file
View File

@ -0,0 +1 @@
export { RootNavigation } from './RootNavigation';

View File

@ -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,
},
});

View File

@ -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',
},
});

View File

@ -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,
},
});

View File

@ -0,0 +1,3 @@
export { Button } from './Button';
export { ContractCard } from './ContractCard';
export { Input } from './Input';

View File

@ -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';

96
src/shared/theme/index.ts Normal file
View File

@ -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,
},
};

74
src/shared/types/index.ts Normal file
View File

@ -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>;
}

29
src/store/authStore.ts Normal file
View File

@ -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,
}),
}));

103
src/store/contractStore.ts Normal file
View File

@ -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 }),
}));

2
src/store/index.ts Normal file
View File

@ -0,0 +1,2 @@
export { useContractStore } from './contractStore';
export { useAuthStore } from './authStore';

30
tsconfig.json Normal file
View File

@ -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"]
}