Merge branch 'feature/stripe' into 'master'

Pay with stripe in app

See merge request edb/dusken-client!2
This commit is contained in:
nikolark committed 2017-11-20 00:29:38 +01:00
commit 65fc376e81
59 files changed
+2832 -1997

No files matched your search

+3 -3
View File
@@ -37,12 +37,12 @@ suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
unsafe.enable_getters_and_setters=true
[version]
^0.53.0
^0.56.0
+4 -1
View File
@@ -50,10 +50,13 @@ buck-out/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
*/fastlane/test_output
android/google_play.json
.env
.env*
*.swp
# Sentry
android/sentry.properties
ios/sentry.properties
ios/Pods
+1 -1
View File
@@ -1,4 +1,4 @@
image: node:latest
image: node:carbon
cache:
paths:
+13 -7
View File
@@ -1,15 +1,11 @@
## Install
# See https://facebook.github.io/react-native/docs/getting-started.html#content
yarn
cd ios; pod install # ios only
# Environment settings
cp env-example .env
# Sentry
# Get auth token from https://sentry.neuf.no/api/ with project:write scope
cp ios/sentry.properties-example ios/sentry.properties
cp android/sentry.properties-example android/sentry.properties
react-native start
react-native run-android
@@ -22,6 +18,13 @@
# Make sure you have hw.keyboard=yes in your avd config.ini (in $HOME/.android/avd/.../) for reloading when pressing R twice to work.
## Prepare release build
# Sentry
# Get auth token from https://sentry.neuf.no/api/ with project:write scope
cp ios/sentry.properties-example ios/sentry.properties
cp android/sentry.properties-example android/sentry.properties
## Create release build (Android)
# Aquire the keystore and put it in android/app/dusken-client.keystore
@@ -32,10 +35,13 @@
DUSKEN_RELEASE_KEY_PASSWORD=
# Build
yarn build-android
yarn build-android # Output is in android/app/build/outputs/apk
# Output is in android/app/build/outputs/apk
# Note: Make sure you have the file android/google_play.json
# Ref: https://docs.fastlane.tools/actions/supply/#setup
# Publish android build to the Google Play beta track
yarn release-android
## Libraries
+1 -1
View File
@@ -1,10 +1,10 @@
import 'react-native';
import React from 'react';
import App from '../src/App';
import 'isomorphic-fetch';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
import 'isomorphic-fetch';
it('renders correctly', () => {
const tree = renderer.create(
+3 -2
View File
@@ -103,8 +103,8 @@ android {
applicationId "no.neuf.chateau"
minSdkVersion 16
targetSdkVersion 23
versionCode 17
versionName "1.1.3"
versionCode 19
versionName "1.2.0"
ndk {
abiFilters "armeabi-v7a", "x86"
}
@@ -159,6 +159,7 @@ android {
}
dependencies {
compile project(':tipsi-stripe')
compile project(':react-native-sentry')
compile project(':react-native-config')
compile fileTree(dir: "libs", include: ["*.jar"])
@@ -3,6 +3,7 @@ package no.neuf.chateau;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.gettipsi.stripe.StripeReactPackage;
import io.sentry.RNSentryPackage;
import com.lugg.ReactNativeConfig.ReactNativeConfigPackage;
import com.facebook.react.ReactInstanceManager;
@@ -26,6 +27,7 @@ public class MainApplication extends Application implements ReactApplication {
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new StripeReactPackage(),
new RNSentryPackage(MainApplication.this),
new ReactNativeConfigPackage()
);
+3 -1
View File
@@ -13,7 +13,9 @@ buildscript {
}
allprojects {
repositories {
repositories {
// Add jitpack repository (added by tipsi-stripe)
maven { url "https://jitpack.io" }
mavenLocal()
jcenter()
maven {
+2
View File
@@ -0,0 +1,2 @@
json_key_file "google_play.json" # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name "no.neuf.chateau" # e.g. com.krausefx.app
+27
View File
@@ -0,0 +1,27 @@
# Customise this file, documentation can be found here:
# https://docs.fastlane.tools/actions/
# All available actions: https://docs.fastlane.tools/actions
# can also be listed using the `fastlane actions` command
# Change the syntax highlighting to Ruby
# All lines starting with a # are ignored when running `fastlane`
# If you want to automatically update fastlane if a new version is available:
# update_fastlane
# This is the minimum version number required.
# Update this, if you use features of a newer version
fastlane_version "2.62.1"
default_platform :android
platform :android do
desc "Submit a new build to the Google Play Beta track"
lane :beta do
# build the release variant
gradle(task: "assembleRelease")
# upload to Google Play
supply(track: "beta")
end
end
+43
View File
@@ -0,0 +1,43 @@
fastlane documentation
================
# Installation
Make sure you have the latest version of the Xcode command line tools installed:
```
xcode-select --install
```
## Choose your installation method:
<table width="100%" >
<tr>
<th width="33%"><a href="http://brew.sh">Homebrew</a></th>
<th width="33%">Installer Script</th>
<th width="33%">RubyGems</th>
</tr>
<tr>
<td width="33%" align="center">macOS</td>
<td width="33%" align="center">macOS</td>
<td width="33%" align="center">macOS or Linux with Ruby 2.0.0 or above</td>
</tr>
<tr>
<td width="33%"><code>brew cask install fastlane</code></td>
<td width="33%"><a href="https://download.fastlane.tools">Download the zip file</a>. Then double click on the <code>install</code> script (or run it in a terminal window).</td>
<td width="33%"><code>sudo gem install fastlane -NV</code></td>
</tr>
</table>
# Available Actions
## Android
### android beta
```
fastlane android beta
```
Submit a new build to the Google Play Beta track
----
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
@@ -0,0 +1,4 @@
You can do the following:
- See your proof of membership
- Buy or renew your membership
- See the event schedule with all of our events
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@@ -0,0 +1 @@
Your proof of membership and the event schedule at Chateau Neuf
@@ -0,0 +1 @@
Chateau Neuf
Whitespace-only changes.
@@ -0,0 +1,4 @@
Du kan gjøre følgende
- Se ditt medlemskapsbevis
- Kjøpe og fornye medlemskap
- Se programmet med alle våre arrangementer
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@@ -0,0 +1 @@
Medlemskapsbevis og program for Chateau Neuf
@@ -0,0 +1 @@
Chateau Neuf - Det Norske Studentersamfund
Whitespace-only changes.
+2
View File
@@ -1,4 +1,6 @@
rootProject.name = 'Dusken'
include ':tipsi-stripe'
project(':tipsi-stripe').projectDir = new File(rootProject.projectDir, '../node_modules/tipsi-stripe/android')
include ':react-native-sentry'
project(':react-native-sentry').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sentry/android')
include ':react-native-config'
+2 -2
View File
@@ -1,5 +1,5 @@
EVENT_API_URL=https://studentersamfundet.no
DUSKEN_API_URL=https://galtinn.neuf.no
DUSKEN_PURCHASE_URL=https://galtinn.neuf.no/?utm_source=purchase-button&utm_medium=app
DUSKEN_FORGOT_PASSWORD_URL=https://galtinn.neuf.no/auth/password_reset/
SENTRY_DSN=
SENTRY_DSN=
STRIPE_PUBLISHABLE_KEY=
+3 -1
View File
@@ -3,6 +3,8 @@ import { AppRegistry } from "react-native";
import { Sentry } from 'react-native-sentry';
import Config from 'react-native-config';
Sentry.config(Config.SENTRY_DSN).install();
if(Config.SENTRY_DSN) {
Sentry.config(Config.SENTRY_DSN).install();
}
AppRegistry.registerComponent('Dusken', () => App);
+186
View File
@@ -39,6 +39,7 @@
3E46ED82B1574BB598025413 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0A6DA7DAB38A43E2BF0FC37C /* EvilIcons.ttf */; };
4306CBFD81DE4035B0AB4D30 /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C02D46B0229941F69264474C /* SimpleLineIcons.ttf */; };
48652E97B0B04C15B3FA6FC3 /* MaterialIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D7A0431FBA3840039633FC3C /* MaterialIcons.ttf */; };
4AF5FEEA965E4967AA857546 /* libTPSStripe.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 95CBCD539DBA4AE7B3D73CD0 /* libTPSStripe.a */; };
551E4CC640DB4E68A88730B5 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4A651C47D6FC4ED2BAB4F197 /* Zocial.ttf */; };
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
65850D7E09F243FDB4F2B168 /* libRNSentry.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E3DF2205F5D4B34BF1CCCCF /* libRNSentry.a */; };
@@ -53,6 +54,7 @@
C867C6D3ECB7499FA2F051D6 /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E926661F17B94D69B50A0BA5 /* FontAwesome.ttf */; };
CC801D520E86402F9AF28660 /* Foundation.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 390970DBF0FD44B0B4C8E2F0 /* Foundation.ttf */; };
D218B6D7605D4C17B6540D0B /* libReactNativeConfig.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 07B9F82FBB154C258C70E333 /* libReactNativeConfig.a */; };
F27CE4F4AEFA492262BF93F8 /* libPods-Dusken.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 584192D9AB197F8F652E4481 /* libPods-Dusken.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -245,6 +247,13 @@
remoteGlobalIDString = 58B5119B1A9E6C1200147676;
remoteInfo = RCTText;
};
871D05431F7303CD0009775B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 32A55FD2F8DE47F3BC5443BB /* TPSStripe.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3A3A7F1A1DC27B520030F9DC;
remoteInfo = TPSStripe;
};
87371BF41F6F25C200915105 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */;
@@ -266,6 +275,34 @@
remoteGlobalIDString = EB2648DF1C7BE17A00B8F155;
remoteInfo = ReactNativeConfig;
};
87A2D0E41FC246C00090CBCD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3DBE0D001F3B181A0099AA32;
remoteInfo = fishhook;
};
87A2D0E61FC246C00090CBCD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3DBE0D0D1F3B181C0099AA32;
remoteInfo = "fishhook-tvOS";
};
87A2D0F61FC246C00090CBCD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 9936F3131F5F2E4B0010BF04;
remoteInfo = privatedata;
};
87A2D0F81FC246C00090CBCD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 9936F32F1F5F2E5B0010BF04;
remoteInfo = "privatedata-tvOS";
};
87E5B2BB1F225BD50051F809 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
@@ -313,6 +350,7 @@
00E356EE1AD99517003FC87E /* DuskenTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DuskenTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* DuskenTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DuskenTests.m; sourceTree = "<group>"; };
020374EEEA8B01E859226AEC /* Pods-Dusken.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Dusken.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Dusken/Pods-Dusken.debug.xcconfig"; sourceTree = "<group>"; };
07B9F82FBB154C258C70E333 /* libReactNativeConfig.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libReactNativeConfig.a; sourceTree = "<group>"; };
0A6DA7DAB38A43E2BF0FC37C /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/native-base/Fonts/EvilIcons.ttf"; sourceTree = "<group>"; };
0D8F73C46ADC4ABEBE691283 /* ReactNativeConfig.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeConfig.xcodeproj; path = "../node_modules/react-native-config/ios/ReactNativeConfig.xcodeproj"; sourceTree = "<group>"; };
@@ -329,9 +367,11 @@
146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = "<group>"; };
2D02E47B1E0B4A5D006451C7 /* Dusken-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Dusken-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
2D02E4901E0B4A5D006451C7 /* Dusken-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Dusken-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
32A55FD2F8DE47F3BC5443BB /* TPSStripe.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = TPSStripe.xcodeproj; path = "../node_modules/tipsi-stripe/ios/TPSStripe.xcodeproj"; sourceTree = "<group>"; };
390970DBF0FD44B0B4C8E2F0 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/native-base/Fonts/Foundation.ttf"; sourceTree = "<group>"; };
4A651C47D6FC4ED2BAB4F197 /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/native-base/Fonts/Zocial.ttf"; sourceTree = "<group>"; };
4E3DF2205F5D4B34BF1CCCCF /* libRNSentry.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSentry.a; sourceTree = "<group>"; };
584192D9AB197F8F652E4481 /* libPods-Dusken.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Dusken.a"; sourceTree = BUILT_PRODUCTS_DIR; };
59C3A3AC725E4DF083440735 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/native-base/Fonts/Ionicons.ttf"; sourceTree = "<group>"; };
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = "<group>"; };
76F0B251D6DD45228CBC367B /* RNSentry.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSentry.xcodeproj; path = "../node_modules/react-native-sentry/ios/RNSentry.xcodeproj"; sourceTree = "<group>"; };
@@ -341,12 +381,15 @@
87E5B29C1F225BD50051F809 /* dnslogo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = dnslogo.png; path = Dusken/dnslogo.png; sourceTree = "<group>"; };
8CAEF677B8A643969E0294B7 /* Roboto_medium.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Roboto_medium.ttf; path = "../node_modules/native-base/Fonts/Roboto_medium.ttf"; sourceTree = "<group>"; };
923192359DEC49D085C78135 /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
94E51283EFA6F590919438A3 /* libPods-DuskenTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-DuskenTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
95CBCD539DBA4AE7B3D73CD0 /* libTPSStripe.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libTPSStripe.a; sourceTree = "<group>"; };
ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTBlob.xcodeproj; path = "../node_modules/react-native/Libraries/Blob/RCTBlob.xcodeproj"; sourceTree = "<group>"; };
C02D46B0229941F69264474C /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/native-base/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; };
C559033577D34FACA86020C1 /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/native-base/Fonts/Octicons.ttf"; sourceTree = "<group>"; };
D4F0955EA08E41F69EEE0DED /* Roboto.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Roboto.ttf; path = "../node_modules/native-base/Fonts/Roboto.ttf"; sourceTree = "<group>"; };
D7A0431FBA3840039633FC3C /* MaterialIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialIcons.ttf; path = "../node_modules/native-base/Fonts/MaterialIcons.ttf"; sourceTree = "<group>"; };
E926661F17B94D69B50A0BA5 /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/native-base/Fonts/FontAwesome.ttf"; sourceTree = "<group>"; };
ED3E665425D2BF39C276B9FC /* Pods-Dusken.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Dusken.release.xcconfig"; path = "Pods/Target Support Files/Pods-Dusken/Pods-Dusken.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -378,6 +421,8 @@
D218B6D7605D4C17B6540D0B /* libReactNativeConfig.a in Frameworks */,
65850D7E09F243FDB4F2B168 /* libRNSentry.a in Frameworks */,
83EBB8CA4CF842BA8C090802 /* libz.tbd in Frameworks */,
4AF5FEEA965E4967AA857546 /* libTPSStripe.a in Frameworks */,
F27CE4F4AEFA492262BF93F8 /* libPods-Dusken.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -465,6 +510,15 @@
name = "Supporting Files";
sourceTree = "<group>";
};
06140F8A90550C16A8941EFD /* Pods */ = {
isa = PBXGroup;
children = (
020374EEEA8B01E859226AEC /* Pods-Dusken.debug.xcconfig */,
ED3E665425D2BF39C276B9FC /* Pods-Dusken.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
139105B71AF99BAD00B5F7CC /* Products */ = {
isa = PBXGroup;
children = (
@@ -479,6 +533,8 @@
children = (
139FDEF41B06529B00C62182 /* libRCTWebSocket.a */,
3DAD3E991DF850E9000B6D8A /* libRCTWebSocket-tvOS.a */,
87A2D0E51FC246C00090CBCD /* libfishhook.a */,
87A2D0E71FC246C00090CBCD /* libfishhook-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
@@ -513,10 +569,21 @@
87E5B2BE1F225BD50051F809 /* libthird-party.a */,
87E5B2C01F225BD50051F809 /* libdouble-conversion.a */,
87E5B2C21F225BD50051F809 /* libdouble-conversion.a */,
87A2D0F71FC246C00090CBCD /* libprivatedata.a */,
87A2D0F91FC246C00090CBCD /* libprivatedata-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
};
3B33B1721C77D1DC86BC30C3 /* Frameworks */ = {
isa = PBXGroup;
children = (
584192D9AB197F8F652E4481 /* libPods-Dusken.a */,
94E51283EFA6F590919438A3 /* libPods-DuskenTests.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
5E91572E1DD0AC6500FF2AA8 /* Products */ = {
isa = PBXGroup;
children = (
@@ -552,6 +619,7 @@
139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */,
0D8F73C46ADC4ABEBE691283 /* ReactNativeConfig.xcodeproj */,
76F0B251D6DD45228CBC367B /* RNSentry.xcodeproj */,
32A55FD2F8DE47F3BC5443BB /* TPSStripe.xcodeproj */,
);
name = Libraries;
sourceTree = "<group>";
@@ -574,6 +642,8 @@
83CBBA001A601CBA00E9B192 /* Products */,
95AA103233A74A28A47B99B8 /* Resources */,
87995FD11F72F740005EB91D /* Recovered References */,
06140F8A90550C16A8941EFD /* Pods */,
3B33B1721C77D1DC86BC30C3 /* Frameworks */,
);
indentWidth = 2;
sourceTree = "<group>";
@@ -591,12 +661,21 @@
name = Products;
sourceTree = "<group>";
};
871D05401F7303CD0009775B /* Products */ = {
isa = PBXGroup;
children = (
871D05441F7303CD0009775B /* libTPSStripe.a */,
);
name = Products;
sourceTree = "<group>";
};
87995FD11F72F740005EB91D /* Recovered References */ = {
isa = PBXGroup;
children = (
07B9F82FBB154C258C70E333 /* libReactNativeConfig.a */,
4E3DF2205F5D4B34BF1CCCCF /* libRNSentry.a */,
923192359DEC49D085C78135 /* libz.tbd */,
95CBCD539DBA4AE7B3D73CD0 /* libTPSStripe.a */,
);
name = "Recovered References";
sourceTree = "<group>";
@@ -670,11 +749,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Dusken" */;
buildPhases = (
D97D7C388D1F900D9E524C3A /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
98D2BF556B534A3FBEC71C2F /* Upload Debug Symbols to Sentry */,
D038498A5851F0B62027363D /* [CP] Embed Pods Frameworks */,
D9A71B546BFA13DD10E69B91 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -816,6 +898,10 @@
ProductGroup = 87995FF41F72F741005EB91D /* Products */;
ProjectRef = 76F0B251D6DD45228CBC367B /* RNSentry.xcodeproj */;
},
{
ProductGroup = 871D05401F7303CD0009775B /* Products */;
ProjectRef = 32A55FD2F8DE47F3BC5443BB /* TPSStripe.xcodeproj */;
},
);
projectRoot = "";
targets = (
@@ -1003,6 +1089,13 @@
remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
871D05441F7303CD0009775B /* libTPSStripe.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libTPSStripe.a;
remoteRef = 871D05431F7303CD0009775B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
87371BF51F6F25C200915105 /* libRCTBlob-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -1024,6 +1117,34 @@
remoteRef = 879C48E21F3A5BB9007436D3 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
87A2D0E51FC246C00090CBCD /* libfishhook.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libfishhook.a;
remoteRef = 87A2D0E41FC246C00090CBCD /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
87A2D0E71FC246C00090CBCD /* libfishhook-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libfishhook-tvOS.a";
remoteRef = 87A2D0E61FC246C00090CBCD /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
87A2D0F71FC246C00090CBCD /* libprivatedata.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libprivatedata.a;
remoteRef = 87A2D0F61FC246C00090CBCD /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
87A2D0F91FC246C00090CBCD /* libprivatedata-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libprivatedata-tvOS.a";
remoteRef = 87A2D0F81FC246C00090CBCD /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
87E5B2BC1F225BD50051F809 /* libthird-party.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -1151,6 +1272,57 @@
shellPath = /bin/sh;
shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/sentry-cli-binary/bin/sentry-cli upload-dsym";
};
D038498A5851F0B62027363D /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Dusken/Pods-Dusken-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
D97D7C388D1F900D9E524C3A /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Dusken-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
D9A71B546BFA13DD10E69B91 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Dusken/Pods-Dusken-resources.sh",
$PODS_CONFIGURATION_BUILD_DIR/Stripe/Stripe.bundle,
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Dusken/Pods-Dusken-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -1228,6 +1400,7 @@
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native-config/ios/ReactNativeConfig",
"$(SRCROOT)/../node_modules/react-native-sentry/ios/**",
"$(SRCROOT)/../node_modules/tipsi-stripe/ios/TPSStripe",
);
INFOPLIST_FILE = DuskenTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
@@ -1236,6 +1409,7 @@
"$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
OTHER_LDFLAGS = (
"-ObjC",
@@ -1255,6 +1429,7 @@
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native-config/ios/ReactNativeConfig",
"$(SRCROOT)/../node_modules/react-native-sentry/ios/**",
"$(SRCROOT)/../node_modules/tipsi-stripe/ios/TPSStripe",
);
INFOPLIST_FILE = DuskenTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
@@ -1263,6 +1438,7 @@
"$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
OTHER_LDFLAGS = (
"-ObjC",
@@ -1275,6 +1451,7 @@
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 020374EEEA8B01E859226AEC /* Pods-Dusken.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1;
@@ -1284,6 +1461,7 @@
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native-config/ios/ReactNativeConfig",
"$(SRCROOT)/../node_modules/react-native-sentry/ios/**",
"$(SRCROOT)/../node_modules/tipsi-stripe/ios/TPSStripe",
);
INFOPLIST_FILE = Dusken/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1300,6 +1478,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = ED3E665425D2BF39C276B9FC /* Pods-Dusken.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1;
@@ -1308,6 +1487,7 @@
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native-config/ios/ReactNativeConfig",
"$(SRCROOT)/../node_modules/react-native-sentry/ios/**",
"$(SRCROOT)/../node_modules/tipsi-stripe/ios/TPSStripe",
);
INFOPLIST_FILE = Dusken/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1338,6 +1518,7 @@
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native-config/ios/ReactNativeConfig",
"$(SRCROOT)/../node_modules/react-native-sentry/ios/**",
"$(SRCROOT)/../node_modules/tipsi-stripe/ios/TPSStripe",
);
INFOPLIST_FILE = "Dusken-tvOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1345,6 +1526,7 @@
"$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
OTHER_LDFLAGS = (
"-ObjC",
@@ -1374,6 +1556,7 @@
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native-config/ios/ReactNativeConfig",
"$(SRCROOT)/../node_modules/react-native-sentry/ios/**",
"$(SRCROOT)/../node_modules/tipsi-stripe/ios/TPSStripe",
);
INFOPLIST_FILE = "Dusken-tvOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1381,6 +1564,7 @@
"$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
OTHER_LDFLAGS = (
"-ObjC",
@@ -1411,6 +1595,7 @@
"$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.Dusken-tvOSTests";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1437,6 +1622,7 @@
"$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.Dusken-tvOSTests";
PRODUCT_NAME = "$(TARGET_NAME)";
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Dusken.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
@@ -1,176 +1,158 @@
{
"images":[
{
"idiom":"iphone",
"size":"20x20",
"scale":"2x",
"filename":"Icon-App-20x20@2x.png"
},
{
"idiom":"iphone",
"size":"20x20",
"scale":"3x",
"filename":"Icon-App-20x20@3x.png"
},
{
"idiom":"iphone",
"size":"29x29",
"scale":"1x",
"filename":"Icon-App-29x29@1x.png"
},
{
"idiom":"iphone",
"size":"29x29",
"scale":"2x",
"filename":"Icon-App-29x29@2x.png"
},
{
"idiom":"iphone",
"size":"29x29",
"scale":"3x",
"filename":"Icon-App-29x29@3x.png"
},
{
"idiom":"iphone",
"size":"40x40",
"scale":"1x",
"filename":"Icon-App-40x40@1x.png"
},
{
"idiom":"iphone",
"size":"40x40",
"scale":"2x",
"filename":"Icon-App-40x40@2x.png"
},
{
"idiom":"iphone",
"size":"40x40",
"scale":"3x",
"filename":"Icon-App-40x40@3x.png"
},
{
"idiom":"iphone",
"size":"57x57",
"scale":"1x",
"filename":"Icon-App-57x57@1x.png"
},
{
"idiom":"iphone",
"size":"57x57",
"scale":"2x",
"filename":"Icon-App-57x57@2x.png"
},
{
"idiom":"iphone",
"size":"60x60",
"scale":"1x",
"filename":"Icon-App-60x60@1x.png"
},
{
"idiom":"iphone",
"size":"60x60",
"scale":"2x",
"filename":"Icon-App-60x60@2x.png"
},
{
"idiom":"iphone",
"size":"60x60",
"scale":"3x",
"filename":"Icon-App-60x60@3x.png"
},
{
"idiom":"iphone",
"size":"76x76",
"scale":"1x",
"filename":"Icon-App-76x76@1x.png"
},
{
"idiom":"ipad",
"size":"20x20",
"scale":"1x",
"filename":"Icon-App-20x20@1x.png"
},
{
"idiom":"ipad",
"size":"20x20",
"scale":"2x",
"filename":"Icon-App-20x20@2x.png"
},
{
"idiom":"ipad",
"size":"29x29",
"scale":"1x",
"filename":"Icon-App-29x29@1x.png"
},
{
"idiom":"ipad",
"size":"29x29",
"scale":"2x",
"filename":"Icon-App-29x29@2x.png"
},
{
"idiom":"ipad",
"size":"40x40",
"scale":"1x",
"filename":"Icon-App-40x40@1x.png"
},
{
"idiom":"ipad",
"size":"40x40",
"scale":"2x",
"filename":"Icon-App-40x40@2x.png"
},
{
"size" : "50x50",
"idiom" : "ipad",
"filename" : "Icon-Small-50x50@1x.png",
"scale" : "1x"
},
{
"size" : "50x50",
"idiom" : "ipad",
"filename" : "Icon-Small-50x50@2x.png",
"scale" : "2x"
},
{
"idiom":"ipad",
"size":"72x72",
"scale":"1x",
"filename":"Icon-App-72x72@1x.png"
},
{
"idiom":"ipad",
"size":"72x72",
"scale":"2x",
"filename":"Icon-App-72x72@2x.png"
},
{
"idiom":"ipad",
"size":"76x76",
"scale":"1x",
"filename":"Icon-App-76x76@1x.png"
},
{
"idiom":"ipad",
"size":"76x76",
"scale":"2x",
"filename":"Icon-App-76x76@2x.png"
},
{
"idiom":"ipad",
"size":"76x76",
"scale":"3x",
"filename":"Icon-App-76x76@3x.png"
},
{
"idiom":"ipad",
"size":"83.5x83.5",
"scale":"2x",
"filename":"Icon-App-83.5x83.5@2x.png"
}
],
"info":{
"version":1,
"author":"makeappicon"
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "57x57",
"idiom" : "iphone",
"filename" : "Icon-App-57x57@1x.png",
"scale" : "1x"
},
{
"size" : "57x57",
"idiom" : "iphone",
"filename" : "Icon-App-57x57@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "50x50",
"idiom" : "ipad",
"filename" : "Icon-Small-50x50@1x.png",
"scale" : "1x"
},
{
"size" : "50x50",
"idiom" : "ipad",
"filename" : "Icon-Small-50x50@2x.png",
"scale" : "2x"
},
{
"size" : "72x72",
"idiom" : "ipad",
"filename" : "Icon-App-72x72@1x.png",
"scale" : "1x"
},
{
"size" : "72x72",
"idiom" : "ipad",
"filename" : "Icon-App-72x72@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "app_icon.jpg",
"scale" : "1x"
}
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

+6
View File
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
+2 -2
View File
@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.1.1</string>
<string>1.2.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>6</string>
<string>8</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>comgooglemaps</string>
+5
View File
@@ -0,0 +1,5 @@
platform :ios, '9.0'
target 'Dusken' do
pod 'Stripe', '~> 11.2.0'
end
+12
View File
@@ -0,0 +1,12 @@
PODS:
- Stripe (11.2.0)
DEPENDENCIES:
- Stripe (~> 11.2.0)
SPEC CHECKSUMS:
Stripe: 898f83a95d72180c6eb4acd65b2ae6158fc6a8bd
PODFILE CHECKSUM: 273d0fa628aa24ffc6ca337ff5541ea938e9910c
COCOAPODS: 1.3.1
+2 -201
View File
@@ -1,202 +1,3 @@
/* Workaround ref: https://github.com/facebook/react-native/issues/15810#issuecomment-329940107 */
const mockNativeModules = {
AlertManager: {
alertWithArgs: jest.fn(),
},
AppState: {
addEventListener: jest.fn(),
},
AsyncLocalStorage: {
multiGet: jest.fn((keys, callback) => process.nextTick(() => callback(null, []))),
multiSet: jest.fn((entries, callback) => process.nextTick(() => callback(null))),
multiRemove: jest.fn((keys, callback) => process.nextTick(() => callback(null))),
multiMerge: jest.fn((entries, callback) => process.nextTick(() => callback(null))),
clear: jest.fn(callback => process.nextTick(() => callback(null))),
getAllKeys: jest.fn(callback => process.nextTick(() => callback(null, []))),
},
BuildInfo: {
appVersion: '0',
buildVersion: '0',
},
Clipboard: {
setString: jest.fn(),
},
DataManager: {
queryData: jest.fn(),
},
DeviceInfo: {
Dimensions: {
window: {
fontScale: 2,
height: 1334,
scale: 2,
width: 750,
},
},
},
FacebookSDK: {
login: jest.fn(),
logout: jest.fn(),
queryGraphPath: jest.fn((path, method, params, callback) => callback()),
},
FbRelayNativeAdapter: {
updateCLC: jest.fn(),
},
GraphPhotoUpload: {
upload: jest.fn(),
},
I18n: {
translationsDictionary: JSON.stringify({
'Good bye, {name}!|Bye message': '\u{00A1}Adi\u{00F3}s {name}!',
}),
},
ImageLoader: {
getSize: jest.fn(
(url) => new Promise(() => ({width: 320, height: 240}))
),
prefetchImage: jest.fn(),
},
ImageViewManager: {
getSize: jest.fn(
(uri, success) => process.nextTick(() => success(320, 240))
),
prefetchImage: jest.fn(),
},
KeyboardObserver: {
addListener: jest.fn(),
removeListeners: jest.fn(),
},
Linking: {
openURL: jest.fn(),
canOpenURL: jest.fn(
() => new Promise((resolve) => resolve(true))
),
addEventListener: jest.fn(),
getInitialURL: jest.fn(
() => new Promise((resolve) => resolve())
),
removeEventListener: jest.fn(),
},
LocationObserver: {
getCurrentPosition: jest.fn(),
startObserving: jest.fn(),
stopObserving: jest.fn(),
},
ModalFullscreenViewManager: {},
NetInfo: {
fetch: jest.fn(
() => new Promise((resolve) => resolve())
),
addEventListener: jest.fn(),
isConnected: {
fetch: jest.fn(
() => new Promise((resolve) => resolve())
),
addEventListener: jest.fn(),
},
},
Networking: {
sendRequest: jest.fn(),
abortRequest: jest.fn(),
addListener: jest.fn(),
removeListeners: jest.fn(),
},
PushNotificationManager: {
presentLocalNotification: jest.fn(),
scheduleLocalNotification: jest.fn(),
cancelAllLocalNotifications: jest.fn(),
removeAllDeliveredNotifications: jest.fn(),
getDeliveredNotifications: jest.fn(callback => process.nextTick(() => [])),
removeDeliveredNotifications: jest.fn(),
setApplicationIconBadgeNumber: jest.fn(),
getApplicationIconBadgeNumber: jest.fn(callback => process.nextTick(() => callback(0))),
cancelLocalNotifications: jest.fn(),
getScheduledLocalNotifications: jest.fn(callback => process.nextTick(() => callback())),
requestPermissions: jest.fn(() => Promise.resolve({alert: true, badge: true, sound: true})),
abandonPermissions: jest.fn(),
checkPermissions: jest.fn(callback => process.nextTick(() => callback({alert: true, badge: true, sound: true}))),
getInitialNotification: jest.fn(() => Promise.resolve(null)),
addListener: jest.fn(),
removeListeners: jest.fn(),
},
SourceCode: {
scriptURL: null,
},
StatusBarManager: {
setColor: jest.fn(),
setStyle: jest.fn(),
setHidden: jest.fn(),
setNetworkActivityIndicatorVisible: jest.fn(),
setBackgroundColor: jest.fn(),
setTranslucent: jest.fn(),
},
Timing: {
createTimer: jest.fn(),
deleteTimer: jest.fn(),
},
UIManager: {
AndroidViewPager: {
Commands: {
setPage: jest.fn(),
setPageWithoutAnimation: jest.fn(),
},
},
blur: jest.fn(),
createView: jest.fn(),
dispatchViewManagerCommand: jest.fn(),
focus: jest.fn(),
setChildren: jest.fn(),
manageChildren: jest.fn(),
updateView: jest.fn(),
removeSubviewsFromContainerWithID: jest.fn(),
replaceExistingNonRootView: jest.fn(),
customBubblingEventTypes: {},
customDirectEventTypes: {},
AndroidTextInput: {
Commands: {},
},
ModalFullscreenView: {
Constants: {},
},
ScrollView: {
Constants: {},
},
View: {
Constants: {},
},
},
BlobModule: {
BLOB_URI_SCHEME: 'content',
BLOB_URI_HOST: null,
enableBlobSupport: jest.fn(),
disableBlobSupport: jest.fn(),
createFromParts: jest.fn(),
sendBlob: jest.fn(),
release: jest.fn(),
},
WebSocketModule: {
connect: jest.fn(),
send: jest.fn(),
sendBinary: jest.fn(),
ping: jest.fn(),
close: jest.fn(),
addListener: jest.fn(),
removeListeners: jest.fn(),
},
};
Object.keys(mockNativeModules).forEach(module => {
try {
jest.doMock(module, () => mockNativeModules[module]); // needed by FacebookSDK-test
} catch (e) {
jest.doMock(module, () => mockNativeModules[module], {virtual: true});
}
});
jest.mock('Linking', () => ({
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
openURL: jest.genMockFn().mockReturnValue(Promise.resolve()),
canOpenURL: jest.genMockFn().mockReturnValue(Promise.resolve()),
getInitialURL: jest.genMockFn().mockReturnValue(Promise.resolve()),
jest.doMock('tipsi-stripe', () => ({
init: jest.fn(),
}));
+25 -15
View File
@@ -6,30 +6,40 @@
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest",
"emulator": "emulator -avd $(emulator -list-avds | head)&",
"build-android": "cd android && ./gradlew assembleRelease"
"build-android": "cd android && ./gradlew assembleRelease",
"release-android": "cd android && fastlane beta"
},
"dependencies": {
"apollo-cache-inmemory": "^1.1.0",
"apollo-client": "^2.0.2",
"apollo-link-http": "^1.1.0",
"email-validator": "^1.1.1",
"graphql": "^0.11.7",
"graphql-tag": "^2.5.0",
"moment": "^2.19.1",
"native-base": "^2.3.2",
"native-base": "^2.3.3",
"prop-types": "^15.5.10",
"react": "16.0.0-beta.5",
"react-native": "0.49.1",
"react": "16.0.0",
"react-apollo": "^2.0.0",
"react-native": "0.50.3",
"react-native-confetti": "^0.0.6",
"react-native-config": "^0.6.1",
"react-native-htmlview": "^0.12.0",
"react-native-sentry": "^0.23.2",
"react-navigation": "^1.0.0-beta.11",
"react-native-config": "^0.10.0",
"react-native-htmlview": "^0.12.1",
"react-native-sentry": "^0.30.2",
"react-native-vector-icons": "~4.4.2",
"react-navigation": "^1.0.0-beta.19",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
"redux-persist": "^4.9.1",
"redux-thunk": "^2.2.0"
"redux-persist": "^4.10.2",
"redux-thunk": "^2.2.0",
"tipsi-stripe": "^4.1.0"
},
"devDependencies": {
"babel-jest": "20.0.3",
"babel-preset-react-native": "1.9.2",
"jest": "20.0.4",
"react-test-renderer": "16.0.0-alpha.12"
"babel-jest": "21.2.0",
"jest": "21.2.1",
"react-dom": "^16.0.0",
"react-test-renderer": "16.1.1"
},
"jest": {
"preset": "react-native",
@@ -37,7 +47,7 @@
"<rootDir>/jest/setup.js"
],
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation|native-base|@shoutem)"
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation|native-base|@shoutem|tipsi-stripe)"
]
}
}
+19 -5
View File
@@ -1,24 +1,38 @@
import Dusken from "./Dusken";
import React, {Component} from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import {Provider} from 'react-redux';
import {applyMiddleware, createStore} from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import {persistStore, autoRehydrate} from 'redux-persist'
import {autoRehydrate, persistStore} from 'redux-persist'
import {AsyncStorage} from 'react-native'
import Config from 'react-native-config';
import {ApolloClient} from 'apollo-client';
import {HttpLink} from 'apollo-link-http';
import {InMemoryCache} from 'apollo-cache-inmemory';
import {ApolloProvider} from "react-apollo";
import Dusken from "./Dusken";
import duskenApp from "./reducers";
let store = createStore(
duskenApp,
applyMiddleware(...[logger, thunk]),
autoRehydrate()
);
const apolloClient = new ApolloClient({
link: new HttpLink({uri: `${Config.DUSKEN_API_URL}/api/graphql/`}),
cache: new InMemoryCache()
});
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Dusken />
<ApolloProvider client={apolloClient}>
<Dusken />
</ApolloProvider>
</Provider>
)
}
+9 -46
View File
@@ -1,52 +1,15 @@
import React from 'react';
import React, {Component} from 'react';
import {connect} from "react-redux";
import {Platform} from 'react-native';
import {TabNavigator, StackNavigator} from "react-navigation";
import AboutScreen from "./screens/AboutScreen";
import EventDetailScreen from "./screens/EventDetailScreen";
import EventListScreen from "./screens/EventListScreen";
import LoginScreen from "./screens/LoginScreen";
import MembershipScreen from "./screens/MembershipScreen";
import {Root} from 'native-base';
import DuskenNavigation from "./navigation";
/* Navigation */
const tabBarRoutes = {
EventList: { screen: EventListScreen },
Membership: { screen: MembershipScreen },
About: { screen: AboutScreen },
};
const tabBarOptions = {
tabBarPosition: 'bottom',
tabBarOptions: {
style: {
backgroundColor: "#fff"
},
indicatorStyle: {
backgroundColor: "#f58220"
},
activeTintColor: '#f58220',
inactiveTintColor: '#333333',
showIcon: true,
labelStyle: {
fontSize: 10,
marginBottom: 0,
...Platform.select({
android: {
fontWeight: 'bold',
}
}),
},
class DuskenContainer extends Component {
render() {
return (<Root>
<DuskenNavigation/>
</Root>)
}
};
const tabNav = TabNavigator(tabBarRoutes, tabBarOptions);
const DuskenContainer = StackNavigator({
Root: { screen: tabNav },
Login: { screen: LoginScreen },
EventDetail: { screen: EventDetailScreen },
});
}
export default connect()(DuskenContainer);
+52 -70
View File
@@ -1,5 +1,3 @@
import Config from 'react-native-config';
const LOGIN_REQUEST = 'LOGIN_REQUEST';
const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
const LOGIN_FAILURE = 'LOGIN_FAILURE';
@@ -7,6 +5,12 @@ const LOGOUT = 'LOGOUT';
const USER_DATA_REQUEST = 'USER_DATA_REQUEST';
const USER_DATA_SUCCESS = 'USER_DATA_SUCCESS';
const USER_DATA_FAILURE = 'USER_DATA_FAILURE';
const REGISTER_USER_REQUEST = 'REGISTER_USER_REQUEST';
const REGISTER_USER_SUCCESS = 'REGISTER_USER_SUCCESS';
const REGISTER_USER_FAILURE = 'REGISTER_USER_FAILURE';
const MEMBERSHIP_CHARGE_REQUEST = 'MEMBERSHIP_CHARGE_REQUEST';
const MEMBERSHIP_CHARGE_SUCCESS = 'MEMBERSHIP_CHARGE_SUCCESS';
const MEMBERSHIP_CHARGE_FAILURE = 'MEMBERSHIP_CHARGE_FAILURE';
function loginRequest() {
return {
@@ -55,6 +59,45 @@ function userDataFailure(userError) {
}
}
function registerUserRequest() {
return {
type: REGISTER_USER_REQUEST
}
}
function registerUserSuccess(data) {
return {
type: REGISTER_USER_SUCCESS,
data
}
}
function registerUserFailure(registerError) {
return {
type: REGISTER_USER_FAILURE,
registerError
}
}
function membershipChargeRequest() {
return {
type: MEMBERSHIP_CHARGE_REQUEST
}
}
function membershipChargeSuccess(data) {
return {
type: MEMBERSHIP_CHARGE_SUCCESS,
data
}
}
function membershipChargeFailure(chargeError) {
return {
type: MEMBERSHIP_CHARGE_FAILURE,
chargeError
}
}
export {
LOGIN_REQUEST, loginRequest,
LOGIN_SUCCESS, loginSuccess,
@@ -63,71 +106,10 @@ export {
USER_DATA_REQUEST, userDataRequest,
USER_DATA_SUCCESS, userDataSuccess,
USER_DATA_FAILURE, userDataFailure,
};
export function requestLogin(username, password) {
return (dispatch) => {
// We are now logging in
dispatch(loginRequest());
const apiURL = Config.DUSKEN_API_URL;
return fetch(`${apiURL}/auth/obtain-token/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
'username': username,
'password': password
})
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(loginSuccess(username, data.token))
},
data => {
console.log(data);
dispatch(loginFailure(data || {'non_field_errors': ['Kunne ikke logge inn, prøv igjen']}))
}
)
}
}
export function requestUserData(token) {
return (dispatch) => {
// We are now fetching user data
dispatch(userDataRequest());
const apiURL = Config.DUSKEN_API_URL;
return fetch(`${apiURL}/api/me/`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Token ' + token
}
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(userDataSuccess(data))
},
data => {
dispatch(userDataFailure('Det funka ikke :-('))
}
)
}
}
REGISTER_USER_REQUEST, registerUserRequest,
REGISTER_USER_SUCCESS, registerUserSuccess,
REGISTER_USER_FAILURE, registerUserFailure,
MEMBERSHIP_CHARGE_REQUEST, membershipChargeRequest,
MEMBERSHIP_CHARGE_SUCCESS, membershipChargeSuccess,
MEMBERSHIP_CHARGE_FAILURE, membershipChargeFailure,
};
+147
View File
@@ -0,0 +1,147 @@
import Config from 'react-native-config';
import {
loginFailure, loginRequest, loginSuccess,
registerUserFailure, registerUserRequest, registerUserSuccess,
userDataFailure, userDataRequest, userDataSuccess,
membershipChargeFailure, membershipChargeRequest, membershipChargeSuccess,
} from "./actions";
export function requestLogin(username, password) {
return (dispatch) => {
// We are now logging in
dispatch(loginRequest());
return fetch(`${Config.DUSKEN_API_URL}/auth/obtain-token/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
'username': username,
'password': password
})
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(loginSuccess(username, data.token))
},
data => {
console.log(data);
dispatch(loginFailure(data || {'non_field_errors': ['Kunne ikke logge inn, prøv igjen']}))
}
)
}
}
export function requestUserData(auth_token) {
return (dispatch) => {
// We are now fetching user data
dispatch(userDataRequest());
return fetch(`${Config.DUSKEN_API_URL}/api/me/`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Token ${auth_token}`
}
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(userDataSuccess(data))
},
data => {
dispatch(userDataFailure(data || {'non_field_errors': ['Kunne ikke hente brukerdata']}))
}
)
}
}
export function requestRegisterUser(firstName, lastName, email, phoneNumber, password) {
return (dispatch) => {
dispatch(registerUserRequest());
return fetch(`${Config.DUSKEN_API_URL}/api/user/register/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
'first_name': firstName,
'last_name': lastName,
'email': email,
'phone_number': phoneNumber,
'password': password
})
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(registerUserSuccess(data))
},
data => {
dispatch(registerUserFailure(data || {'non_field_errors': ['Kunne ikke registrere bruker, prøv igjen']}))
}
)
}
}
export function requestMembershipCharge(auth_token, stripe_token, membership_type) {
return (dispatch) => {
dispatch(membershipChargeRequest());
return fetch(`${Config.DUSKEN_API_URL}/api/membership/charge/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Token ${auth_token}`
},
body: JSON.stringify({
'stripe_token': stripe_token,
'membership_type': membership_type,
'payment_method': 'app',
})
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(membershipChargeSuccess(data))
},
data => {
dispatch(membershipChargeFailure(data || {'non_field_errors': ['Kunne ikke belaste kort, prøv igjen']}))
}
)
}
}
+2 -18
View File
@@ -3,6 +3,7 @@ import {Text, ScrollView, View, Platform, StyleSheet, Image, Linking, TouchableO
// import HTMLView from 'react-native-htmlview';
import {Button, Text as NBText, Icon} from 'native-base';
import theme from "../../theme";
const CHATEAU_NEUF_ADDRESS = 'Det Norske Studentersamfund, Slemdalsveien 15, 0369 Oslo';
@@ -125,24 +126,7 @@ const styles = StyleSheet.create({
paddingBottom: 10,
lineHeight: 25,
},
card: {
borderColor: '#e1e8ee',
borderWidth: 1,
...Platform.select({
ios: {
shadowColor: 'rgba(0,0,0, .2)',
shadowOffset: {height: 0, width: 0},
shadowOpacity: 1,
shadowRadius: 1,
},
android: {
elevation: 1,
},
}),
padding: 8,
margin: 8,
backgroundColor: '#fff',
},
card: theme.card,
featuredImage: {
height: 230,
width: null,
+7 -36
View File
@@ -1,9 +1,9 @@
import React, { Component } from 'react';
import {Linking, StyleSheet, ScrollView, Text, Platform, View, Image, TouchableHighlight,
TouchableOpacity} from "react-native";
import {Linking, StyleSheet, ScrollView, Text, Platform, View, Image, TouchableOpacity} from "react-native";
import HTMLView from 'react-native-htmlview';
import moment from "moment";
import {Button, Icon, Text as NBText} from "native-base";
import {Icon} from "native-base";
import theme from "../../theme";
moment.locale('nb');
@@ -172,7 +172,7 @@ export default class EventDetail extends Component {
{/* Date and title */}
<View style={styles.dateAndTitle}>
<View style={styles.dateContainer}>
<View style={styles.dateCard}>
<View style={[styles.card, styles.dateCard]}>
<Text style={styles.day}>{this._formatDay(item.start_time)}</Text>
<Text style={styles.month}>{this._formatMonth(item.start_time)}</Text>
{this.showYear()}
@@ -222,24 +222,7 @@ const styles = StyleSheet.create({
fontWeight: 'bold',
color: '#000',
},
card: {
borderColor: '#e1e8ee',
borderWidth: 1,
...Platform.select({
ios: {
shadowColor: 'rgba(0,0,0, .2)',
shadowOffset: {height: 0, width: 0},
shadowOpacity: 1,
shadowRadius: 1,
},
android: {
elevation: 1,
},
}),
padding: 8,
margin: 8,
backgroundColor: '#fff',
},
card: theme.card,
image: {
height: 260,
width: null
@@ -275,20 +258,8 @@ const styles = StyleSheet.create({
justifyContent: 'center',
},
dateCard: {
borderColor: '#e1e8ee',
borderWidth: 1,
...Platform.select({
ios: {
shadowColor: 'rgba(0,0,0, .2)',
shadowOffset: {height: 0, width: 0},
shadowOpacity: 2,
shadowRadius: 2,
},
android: {
elevation: 1,
},
}),
backgroundColor: '#fff',
margin: 0,
padding: 0,
paddingBottom: 6,
width: 62,
},
+28 -9
View File
@@ -1,8 +1,9 @@
import {StyleSheet, ActivityIndicator, View, SectionList} from "react-native";
import {Card, ListItem, CardItem, Body, Text as Text, Left, Right, Icon, Button, Content} from 'native-base';
import {StyleSheet, View, SectionList, Platform} from "react-native";
import {Card, ListItem, CardItem, Body, Text as Text, Left, Right, Icon, Button, Content, Spinner} from 'native-base';
import React, { Component } from 'react';
import moment from "moment";
import 'moment/locale/nb';
import theme from "../../theme";
moment.locale('nb');
@@ -20,9 +21,9 @@ export default class EventList extends Component {
);
_renderSectionHeader = ({section}) => {
return (<ListItem style={[styles.listItem, {paddingBottom: 8}]}>
return (<ListItem style={styles.sectionHeader}>
<Body>
<Text style={styles.listItemSectionTitle}>{section.title}</Text>
<Text style={styles.sectionTitle}>{section.title}</Text>
</Body>
</ListItem>);
};
@@ -33,7 +34,7 @@ export default class EventList extends Component {
}
return (<View style={{paddingVertical: 20}}>
<ActivityIndicator animating size="large" />
<Spinner color="#f58220"/>
</View>);
};
@@ -71,7 +72,8 @@ export default class EventList extends Component {
onEndReachedThreshould={10}
initialNumToRender={10}
ListFooterComponent={this._renderFooter}
style={styles.list}
style={[styles.card, styles.list]}
ListHeaderComponent={() => { return <View style={styles.listHeader}/> }}
/>);
}
}
@@ -79,6 +81,7 @@ export default class EventList extends Component {
const styles = StyleSheet.create({
listItem: {
backgroundColor: 'white',
marginLeft: 0
},
listItemTitle: {
@@ -88,15 +91,31 @@ const styles = StyleSheet.create({
fontSize: 14,
color: '#666'
},
listItemSectionTitle: {
fontSize: 14,
sectionHeader: {
backgroundColor: 'white',
marginLeft: 0,
...Platform.select({
'ios': {marginBottom: -8},
}),
paddingBottom: 0,
borderBottomWidth: 0,
},
sectionTitle: {
fontSize: 16,
fontWeight: 'bold',
},
loadingText: {
textAlign: 'center',
paddingBottom: 8
},
card: theme.card,
list: {
backgroundColor: 'white'
padding: 0,
marginTop: 0,
marginBottom: 0,
backgroundColor: 'transparent',
},
listHeader: {
paddingTop: 8,
}
});
+2 -18
View File
@@ -3,6 +3,7 @@ import {Linking, StyleSheet, View, Platform} from 'react-native';
import {Container, Header, Content, Form, Item, Input, Label, Spinner, Button, Text,
Icon} from 'native-base';
import Config from 'react-native-config';
import theme from "../../theme";
export default class Login extends Component {
constructor(props) {
@@ -157,22 +158,5 @@ const styles = StyleSheet.create({
textAlign: 'center',
color: 'red',
},
card: {
borderColor: '#e1e8ee',
borderWidth: 1,
...Platform.select({
ios: {
shadowColor: 'rgba(0,0,0, .2)',
shadowOffset: {height: 0, width: 0},
shadowOpacity: 1,
shadowRadius: 1,
},
android: {
elevation: 1,
},
}),
padding: 8,
margin: 8,
backgroundColor: '#fff',
},
card: theme.card,
});
+7 -22
View File
@@ -1,42 +1,27 @@
import React, {Component} from 'react';
import {connect} from "react-redux";
import Login from "./Login";
import {requestLogin} from "../../actions";
import {requestLogin} from "../../api";
class LoginContainer extends Component {
constructor(state) {
super(state);
this.state = {
isAuthenticated: false,
isLoggingIn: false,
loginError: null,
}
}
componentWillReceiveProps(nextProps) {
if (this.state.isAuthenticated !== nextProps.isAuthenticated) {
if (this.props.isAuthenticated !== nextProps.isAuthenticated) {
if (nextProps.isAuthenticated) {
this.props.onLogin();
}
}
if (this.state.isLoggingIn !== nextProps.isLoggingIn) {
this.setState({isLoggingIn: nextProps.isLoggingIn})
}
if (this.state.loginError !== nextProps.loginError) {
this.setState({loginError: nextProps.loginError})
}
}
render() {
return <Login {...this.state} requestLogin={this.props.requestLogin}/>
return <Login {...this.props} />
}
}
export default connect(
(state) => ({
isAuthenticated: state.isAuthenticated,
isLoggingIn: state.isLoggingIn,
loginError: state.loginError,
(store) => ({
isAuthenticated: store.isAuthenticated,
isLoggingIn: store.isLoggingIn,
loginError: store.loginError,
}),
{requestLogin}
)(LoginContainer);
+16 -26
View File
@@ -1,17 +1,27 @@
import React, {Component} from 'react';
import {ScrollView, Text, View, Linking, StyleSheet, Platform} from "react-native";
import {Button, Text as NBText} from "native-base";
import Config from 'react-native-config';
import {ScrollView, Text, View, StyleSheet} from "react-native";
import {Button, Text as NBText, Spinner} from "native-base";
import theme from "../../theme";
export default class Membership extends Component {
render() {
if (this.props.isFetchingUserData) {
return (
<View style={styles.card}>
<Spinner color="#f58220"/>
</View>
);
}
return (
<ScrollView>
<View style={styles.card}>
<Text style={styles.headerTitle}>Hei 👋</Text>
<Text style={styles.paragraph}>Her kan du bli medlem i Det Norske Studentersamfund - Chateau Neuf og ha medlemsbeviset ditt på telefonen</Text>
<View style={styles.button}>
<Button onPress={() => { Linking.openURL(Config.DUSKEN_PURCHASE_URL); } } full><NBText>Bli medlem</NBText></Button>
<Button onPress={this.props.onRegisterPress} full><NBText>Bli medlem</NBText></Button>
</View>
<Text style={styles.header}>Hva får jeg?</Text>
<Text style={[styles.paragraph, {textAlign: 'left', paddingLeft: 4}]}>
@@ -22,38 +32,18 @@ export default class Membership extends Component {
- Mulighet til å delta i landets eldste studentdemokrati</Text>
<Text style={styles.em}>Allerede medlem?</Text>
<View style={styles.button}>
<Button onPress={this.onLoginPress} full><NBText>Logg inn</NBText></Button>
<Button onPress={this.props.onLoginPress} full><NBText>Logg inn</NBText></Button>
</View>
</View>
</ScrollView>
)
};
onLoginPress = () => {
this.props.onLoginPress();
};
}
const styles = StyleSheet.create({
card: {
borderColor: '#e1e8ee',
borderWidth: 1,
...Platform.select({
ios: {
shadowColor: 'rgba(0,0,0, .2)',
shadowOffset: {height: 0, width: 0},
shadowOpacity: 1,
shadowRadius: 1,
},
android: {
elevation: 1,
},
}),
padding: 8,
margin: 8,
backgroundColor: '#fff',
},
card: theme.card,
paragraph: {
textAlign: 'center',
fontSize: 16,
+39 -23
View File
@@ -1,26 +1,23 @@
import React from 'react';
import React, {Component} from 'react';
import {connect} from "react-redux";
import Membership from "./Membership";
import MembershipProof from "./MembershipProof";
import { requestUserData, logout } from "../../actions";
import ProofContainer from "./ProofContainer";
import { logout } from "../../actions";
import {requestUserData} from "../../api";
class MembershipContainer extends React.Component {
class MembershipContainer extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: props.isAuthenticated,
userToken: props.userToken,
isFetchingUserData: props.isFetchingUserData,
user: props.user,
userToken: props.userToken,
lastOrder: props.lastOrder,
}
}
componentWillReceiveProps(nextProps) {
if(this.state.isAuthenticated !== nextProps.isAuthenticated) {
this.setState({isAuthenticated: nextProps.isAuthenticated})
}
if(this.state.userToken !== nextProps.userToken) {
this.setState({userToken: nextProps.userToken}, () => {
if(nextProps.userToken) {
@@ -29,8 +26,12 @@ class MembershipContainer extends React.Component {
});
}
if(this.state.user !== nextProps.user) {
this.setState({user: nextProps.user})
if(this.state.lastOrder !== nextProps.lastOrder) {
this.setState({lastOrder: nextProps.lastOrder}, () => {
if(nextProps.userToken) {
this.fetchUser();
}
});
}
}
@@ -42,29 +43,44 @@ class MembershipContainer extends React.Component {
this.props.onLoginPress();
};
onRegisterPress = () => {
this.props.onRegisterPress();
};
onLogoutPress = () => {
this.props.logout();
this.props.logoutNavigate();
this.props.onLogoutPress();
};
render() {
if (this.state.user) {
return <MembershipProof
user={this.state.user}
if (this.props.user) {
return <ProofContainer
user={this.props.user}
isAuthenticated={this.props.isAuthenticated}
userToken={this.state.userToken}
fetchUser={this.fetchUser}
onLogoutPress={this.onLogoutPress}
/>;
}
return <Membership onLoginPress={this.onLoginPress}/>;
return <Membership
isFetchingUserData={this.props.isFetchingUserData}
onLoginPress={this.onLoginPress}
onRegisterPress={this.onRegisterPress}
/>;
}
}
function storeToProps(store) {
return {
isAuthenticated: store.isAuthenticated,
isFetchingUserData: store.isFetchingUserData,
userToken: store.userToken,
user: store.user,
lastOrder: store.lastOrder
};
}
export default connect(
(state) => ({
isAuthenticated: state.isAuthenticated,
isFetchingUserData: state.isFetchingUserData,
userToken: state.userToken,
user: state.user,
}),
storeToProps,
{ requestUserData, logout }
)(MembershipContainer);
@@ -1,27 +1,20 @@
import React, {Component} from 'react';
import {Image, Linking, StyleSheet, Platform, RefreshControl, TouchableOpacity, View} from "react-native";
import {Button, Body, Card, CardItem, Text, Content} from 'native-base';
import Config from 'react-native-config';
import {Image, ScrollView, StyleSheet, Text, TouchableOpacity, View} from "react-native";
import {Button, Text as NBText, Spinner} from 'native-base';
import Confetti from 'react-native-confetti';
export default class MembershipProof extends Component {
import theme from "../../theme";
export default class Proof extends Component {
CONFETTI_TIMEOUT = 15000;
constructor(props) {
super(props);
this.state = {
user: props.user,
refreshing: false,
stopConfetti: false,
}
};
componentWillReceiveProps(nextProps) {
if(this.state.user !== nextProps.user) {
this.setState({user: nextProps.user})
}
}
componentDidUpdate () {
if (this._confettiView && this.state.stopConfetti) {
this._confettiView.stopConfetti();
@@ -35,26 +28,6 @@ export default class MembershipProof extends Component {
}
}
// fetchUser = () => {
// this.props.fetchUser().then(() => {
// this.setState({refreshing: false});
// });
// };
// _onRefresh() {
// console.log('onRefresh')
// this.setState({refreshing: true});
// this.fetchUser()
// }
// refreshControl() {
// return (
// <RefreshControl
// refreshing={this.state.refreshing}
// onRefresh={this._onRefresh.bind(this)}
// />)
// }
onMembershipPress = () => {
this.setState({showConfetti: true}, () => {
if(this._confettiView) {
@@ -67,126 +40,137 @@ export default class MembershipProof extends Component {
};
membershipValidTo() {
if( !this.state.user.last_membership) {
if( !this.props.user.last_membership) {
return;
}
let validTo = this.state.user.last_membership.end_date;
if (this.state.user.last_membership.membership_type === 'lifelong') {
let validTo = this.props.user.last_membership.end_date;
if (this.props.user.last_membership.membership_type === 'lifelong') {
validTo = 'Livsvarig'
}
return (
<CardItem>
<View style={{alignItems: 'center', flex: 1}}>
<Text>Gyldig til: </Text>
<Text style={styles.validToValue}>{validTo}</Text>
</View>
</CardItem>
<View style={{alignItems: 'center', flex: 1}}>
<Text>Gyldig til: </Text>
<Text style={styles.validToValue}>{validTo}</Text>
</View>
)
}
membershipStatus() {
if (!this.state.user.is_member) {
if ( !this.state.user.last_membership ) {
return (
<CardItem>
<View style={styles.notMember}>
<Text style={styles.notMemberText}>Ikke medlem</Text>
</View>
</CardItem>)
if (!this.props.user.is_member) {
if ( !this.props.user.last_membership ) {
return;
}
return <View style={styles.expired}><Text style={styles.expiredText}>Utløpt</Text></View>
return (
<View style={styles.expired}>
<Text style={styles.expiredText}>Utløpt</Text>
</View>)
}
let statusText = 'Medlem';
if (this.state.user.is_volunteer) {
statusText = 'Aktiv';
}
const statusText = this.props.user.is_volunteer ? 'Aktiv' : 'Medlem';
return (
<CardItem>
<View style={styles.membershipStatus}>
<TouchableOpacity onPress={this.onMembershipPress} style={styles.valid}>
<Text style={styles.validText}>{statusText}</Text>
</TouchableOpacity>
</CardItem>);
</View>);
}
purchaseButton() {
if (this.state.user.is_member) {
if (this.props.user.is_member) {
return;
}
return <CardItem>
<Body>
return (
<View>
{this.props.isChargingMembership && <Spinner color="#f58220"/>}
<Button
onPress={() => {
Linking.openURL(Config.DUSKEN_PURCHASE_URL);
}}
onPress={this.props.onChargePress}
full
disabled={this.props.isChargingMembership || this.props.isLoadingMembershipType}
style={styles.purchaseButton}
>
<Text>Kjøp medlemskap</Text>
<NBText>{`Kjøp medlemskap (${this.props.membershipPrice} NOK)`}</NBText>
</Button>
</Body>
</CardItem>
<View style={styles.purchaseText}>
<Text style={styles.purchaseTextInner}>Medlemskapet er gyldig i ett år</Text>
</View>
</View>)
}
confetti() {
if (this.state.user.is_member) {
// FIXME: allmost infinite confetti. Are the animations paused when not visible, if not power hog?
if (this.props.user.is_member) {
// Confetti!!!!
return <Confetti ref={(node) => this._confettiView = node} confettiCount={Number.MAX_SAFE_INTEGER} />
}
}
logo() {
if (!this.state.user.is_member) {
if (!this.props.user.is_member) {
return;
}
// FIXME: URL to config
return <Image
style={{width: null, height: 100}}
source={{uri: 'https://galtinn.neuf.no/static/dist/images/logo.png'}}
resizeMode="contain"
/>
return (
<View style={{marginTop: 8}}>
<Image
style={{width: null, height: 100}}
source={{uri: 'https://galtinn.neuf.no/static/dist/images/logo.png'}}
resizeMode="contain"
/>
</View>)
}
fetchUser = () => {
this.props.fetchUser()
};
chargeError() {
if (!this.props.chargeError) {
return;
}
return <View><Text style={styles.errorMessage}>{this.props.chargeError}</Text></View>
}
render() {
return (
<View style={{flex: 1}}>
<View>
{this.confetti()}
<Content style={{margin: 8}}>
<Card>
<ScrollView>
<View style={[styles.card, {paddingVertical: 16}]}>
{this.membershipName()}
{this.logo()}
{this.membershipStatus()}
{this.chargeError()}
{this.purchaseButton()}
{this.membershipValidTo()}
</Card>
<Button onPress={this.props.onLogoutPress} style={styles.logoutButton} small>
<Text>Logg ut</Text>
</Button>
</Content>
</View>
<View>
<Button onPress={this.props.onLogoutPress} style={styles.logoutButton} small>
<NBText>Logg ut</NBText>
</Button>
</View>
</ScrollView>
</View>
)
};
membershipName() {
let name = `${this.state.user.first_name} ${this.state.user.last_name}`;
let name = `${this.props.user.first_name} ${this.props.user.last_name}`;
if(name === ' ') {
name = this.state.user.email;
name = this.props.user.email;
}
return <CardItem>
return <View>
<Text style={styles.nameText}>{name}</Text>
</CardItem>
</View>
}
}
const styles = StyleSheet.create({
card: theme.card,
membershipStatus: {
marginVertical: 16,
},
nameText: {
flex: 1,
textAlign: 'center',
@@ -208,7 +192,8 @@ const styles = StyleSheet.create({
backgroundColor: '#d9534f',
flex: 1,
marginHorizontal: -16,
padding: 16
padding: 16,
marginVertical: 16,
},
expiredText: {
color: 'white',
@@ -237,4 +222,19 @@ const styles = StyleSheet.create({
alignSelf: 'center',
marginVertical: 20,
},
purchaseText: {
alignItems: 'center'
},
purchaseTextInner: {
fontStyle: 'italic',
fontSize: 16,
textAlign: 'center',
paddingTop: 10,
paddingBottom: 8
},
errorMessage: {
paddingTop: 10,
textAlign: 'center',
color: theme.colors.danger
}
});
+105
View File
@@ -0,0 +1,105 @@
import React, {Component} from 'react';
import {connect} from "react-redux";
import stripe from 'tipsi-stripe'
import Config from 'react-native-config';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import Proof from "./Proof";
import {requestMembershipCharge} from "../../api";
stripe.init({
publishableKey: Config.STRIPE_PUBLISHABLE_KEY || '',
});
class ProofContainer extends Component {
constructor(props) {
super(props);
this.state = {
refreshing: false,
}
}
getPrice(membershipTypes) {
if (!membershipTypes) {
return 0;
}
return membershipTypes[0].price / 100;
}
render() {
return <Proof
{...this.state}
user={this.props.user}
isChargingMembership={this.props.isChargingMembership}
isLoadingMembershipType={this.props.data.loading}
chargeError={this.props.chargeError ? 'Kunne ikke belaste bankkortet, prøv igjen...' : ''}
onChargePress={this.openStripe}
onLogoutPress={this.props.onLogoutPress}
membershipPrice={this.getPrice(this.props.data.membershipTypes)}
/>
}
openStripe = () => {
const options = {
prefilledInformation: {
email: this.props.user.email,
},
smsAutofillDisabled: true,
};
const membershipTypeSlug = this.props.data.membershipTypes ? this.props.data.membershipTypes[0].slug : 'standard';
stripe.paymentRequestWithCardForm(options).then(
(token) => {
// returns a token.tokenId which should be saved
this.props.requestMembershipCharge(
this.props.userToken,
{id: token.tokenId, email: this.props.user.email},
membershipTypeSlug);
},
(error) => { /* FIXME: Happens only if card dialog is canceled? */ }
);
};
// fetchUser = () => {
// this.props.fetchUser().then(() => {
// this.setState({refreshing: false});
// });
// };
// _onRefresh() {
// console.log('onRefresh')
// this.setState({refreshing: true});
// this.fetchUser()
// }
// refreshControl() {
// return (
// <RefreshControl
// refreshing={this.state.refreshing}
// onRefresh={this._onRefresh.bind(this)}
// />)
// }
}
const PROOF_QUERY = gql`{
membershipTypes(isDefault: true) {
slug
price
}
}`;
const proofContainerWithState = connect(
(store) => ({
userToken: store.userToken,
isChargingMembership: store.isChargingMembership,
chargeError: store.chargeError
}),
{requestMembershipCharge}
)(ProofContainer);
export default graphql(PROOF_QUERY)(proofContainerWithState);
+259
View File
@@ -0,0 +1,259 @@
import React, {Component} from 'react';
import {Container, Header, Content, Form, Item, Input, Label, Spinner, Button, Text, Icon, Toast} from 'native-base';
import {StyleSheet, View} from "react-native";
import EmailValidator from 'email-validator';
import theme from "../../theme";
export default class UserRegister extends Component {
constructor(props) {
super(props);
this.state = {
firstName: '',
lastName: '',
email: '',
phoneNumber: '',
password: '',
errors: {},
touched: new Set()
}
};
validateForm(triggeredBy) {
const fieldNames = ['firstName', 'lastName', 'email', 'phoneNumber', 'password'];
let errors = Object.assign({}, this.state.errors);
let touched = new Set(this.state.touched).add(triggeredBy);
/* No empty fields */
for(let key of fieldNames) {
const value = this.state[key];
if(value === '') {
errors[key] = 'kan ikke være tomt';
} else if(key === 'email' && !EmailValidator.validate(value)) {
errors[key] = 'er ikke en gyldig e-post';
} else {
delete errors[key];
}
}
this.setState({errors: errors, touched});
};
fieldHasError(field) {
return this.state.touched.has(field) && field in this.state.errors;
}
canSubmitForm() {
return Object.keys(this.state.errors).length === 0;
}
firstNameInput() {
return <Item error={this.fieldHasError('firstName')}>
<Label>Fornavn</Label>
<Input
autoFocus={true}
returnKeyType="next"
onChangeText={(firstName) => {
this.setState({firstName}, () => { this.validateForm('firstName'); });
}}
onSubmitEditing={() => {
this.refs.lastNameInput._root.focus()
}}
value={this.state.firstName}
/>
{this.fieldHasError('firstName') && <Icon name='close-circle' />}
</Item>;
}
lastNameInput() {
return <Item error={this.fieldHasError('lastName')}>
<Label>Etternavn</Label>
<Input
ref="lastNameInput"
returnKeyType="next"
onChangeText={(lastName) => {
this.setState({lastName}, () => { this.validateForm('lastName'); });
}}
onSubmitEditing={() => {
this.refs.emailInput._root.focus()
}}
value={this.state.lastName}
/>
{this.fieldHasError('lastName') && <Icon name='close-circle' />}
</Item>;
}
emailInput() {
return <Item error={this.fieldHasError('email')}>
<Label>E-post</Label>
<Input
ref="emailInput"
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
returnKeyType="next"
onChangeText={(email) => {
this.setState({email}, () => { this.validateForm('email'); });
}}
onSubmitEditing={() => {
this.refs.phoneInput._root.focus()
}}
value={this.state.email}
/>
{this.fieldHasError('email') && <Icon name='close-circle' />}
</Item>;
}
phoneNumberInput() {
return <Item error={this.fieldHasError('phoneNumber')}>
<Label>Mobilnummer</Label>
<Input
ref="phoneInput"
returnKeyType="next"
keyboardType="phone-pad"
onChangeText={(phoneNumber) => {
this.setState({phoneNumber}, () => { this.validateForm('phoneNumber'); });
}}
onSubmitEditing={() => {
this.refs.passwordInput._root.focus()
}}
value={this.state.phoneNumber}
/>
{this.fieldHasError('phoneNumber') && <Icon name='close-circle' />}
</Item>;
}
passwordInput() {
return <Item error={this.fieldHasError('password')}>
<Label>Passord</Label>
<Input
ref="passwordInput"
secureTextEntry={true}
autoCapitalize="none"
autoCorrect={false}
onChangeText={(password) => {
this.setState({password}, () => { this.validateForm('password'); });
}}
onSubmitEditing={this.onRegisterPress}
value={this.state.password}
/>
{this.fieldHasError('password') && <Icon name='close-circle' />}
</Item>
}
componentWillReceiveProps(nextProps) {
if(this.props.registerError !== nextProps.registerError) {
this.setState({'errors': nextProps.registerError})
}
}
showNonFieldError = () => {
const errors = this.props.registerError;
if (Object.keys(errors).length !== 0 && 'non_field_errors' in errors) {
const err = errors.non_field_errors;
let errorFormatted = err ? err[0] : 'Kunne ikke registrere bruker, prøv igjen...';
return <View style={styles.errorBox}><Text style={styles.errorMessage}>{errorFormatted}</Text></View>
}
return <View style={styles.errorBox}/>
};
showFieldError = (field) => {
if(!this.fieldHasError(field) ) {
return;
}
return <Item style={styles.errorText}><Text style={styles.errorMessage}>{this.state.errors[field]}</Text></Item>;
};
showSpinner = () => {
if (this.props.isRegisteringUser) {
return <Spinner color="#f58220"/>
}
};
render() {
return (
<Container style={styles.container}>
<Content keyboardShouldPersistTaps='always'>
<Form style={styles.card}>
{this.firstNameInput()}
{this.showFieldError('firstName')}
{this.lastNameInput()}
{this.showFieldError('lastName')}
{this.emailInput()}
{this.showFieldError('email')}
{this.phoneNumberInput()}
{this.showFieldError('phoneNumber')}
{this.passwordInput()}
{this.showFieldError('password')}
{this.showNonFieldError()}
{this.registerbutton()}
{this.showSpinner()}
</Form>
</Content>
</Container>
);
}
onRegisterPress = () => {
if( !this.canSubmitForm() ) {
Toast.show({
text: 'Noen av feltene er ikke fylt ut riktig',
position: 'bottom',
buttonText: 'OK',
duration: 1500,
});
return;
}
this.props.onRegisterPress(
this.state.firstName,
this.state.lastName,
this.state.email,
this.state.phoneNumber,
this.state.password,
);
};
registerbutton() {
const isDisabled = this.state.touched.size === 0 || this.props.isRegisteringUser || !this.canSubmitForm();
return (<Button
full
disabled={isDisabled}
onPress={this.onRegisterPress}
style={styles.registerButton}
>
<Text>Registrer meg</Text>
</Button>);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
input: {
width: 320,
fontSize: 20,
height: 52,
},
registerButton: {
marginBottom: 16,
},
errorBox: {
marginVertical: 10,
},
errorMessage: {
textAlign: 'center',
color: theme.colors.danger,
fontSize: 14,
},
card: theme.card,
errorText: {
paddingTop: 2,
paddingBottom: 12,
}
});
@@ -0,0 +1,48 @@
import React, {Component} from 'react';
import {connect} from "react-redux";
import UserRegister from "./UserRegister";
import {requestRegisterUser} from "../../api";
class UserRegisterContainer extends Component {
constructor(state) {
super(state);
this.state = {
isAuthenticated: false,
}
}
componentWillReceiveProps(nextProps) {
if (this.state.isAuthenticated !== nextProps.isAuthenticated) {
if (nextProps.isAuthenticated) {
this.props.onRegister();
}
}
}
onRegisterPress = (firstName, lastName, email, phoneNumber, password) => {
this.props.requestRegisterUser(firstName, lastName, email, phoneNumber, password);
};
render() {
return <UserRegister
{...this.state}
onRegisterPress={this.onRegisterPress}
registerError={this.props.registerError}
isRegisteringUser={this.props.isRegisteringUser}
/>;
}
}
function storeToProps(store) {
const errors = store.registerError === null ? {} : store.registerError;
return {
isAuthenticated: store.isAuthenticated,
isRegisteringUser: store.isRegisteringUser,
registerError: errors
};
}
export default connect(
storeToProps,
{requestRegisterUser}
)(UserRegisterContainer);
+56
View File
@@ -0,0 +1,56 @@
import {Platform, StyleSheet} from 'react-native';
import {TabNavigator, StackNavigator} from "react-navigation";
import AboutScreen from "./screens/AboutScreen";
import EventDetailScreen from "./screens/EventDetailScreen";
import EventListScreen from "./screens/EventListScreen";
import LoginScreen from "./screens/LoginScreen";
import MembershipScreen from "./screens/MembershipScreen";
import UserRegisterScreen from "./screens/UserRegisterScreen";
const tabBarRoutes = {
EventList: { screen: EventListScreen },
Membership: { screen: MembershipScreen },
About: { screen: AboutScreen },
};
const tabBarOptions = {
tabBarPosition: 'bottom',
tabBarOptions: {
style: {
...Platform.select({
android: {
backgroundColor: "#fff",
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: 'rgba(0, 0, 0, 0.1)'
}
})
},
indicatorStyle: {
backgroundColor: "#f58220"
},
activeTintColor: '#f58220',
inactiveTintColor: '#333333',
showIcon: true,
labelStyle: {
fontSize: 10,
marginBottom: 0,
...Platform.select({
android: {
fontWeight: 'bold',
}
}),
},
}
};
const tabNav = TabNavigator(tabBarRoutes, tabBarOptions);
const DuskenNavigation = StackNavigator({
Root: { screen: tabNav },
Login: { screen: LoginScreen },
UserRegister: { screen: UserRegisterScreen },
EventDetail: { screen: EventDetailScreen },
});
export default DuskenNavigation;
+70 -2
View File
@@ -6,20 +6,42 @@ import {
USER_DATA_REQUEST,
USER_DATA_SUCCESS,
USER_DATA_FAILURE,
REGISTER_USER_REQUEST,
REGISTER_USER_FAILURE,
REGISTER_USER_SUCCESS, MEMBERSHIP_CHARGE_REQUEST, MEMBERSHIP_CHARGE_FAILURE, MEMBERSHIP_CHARGE_SUCCESS,
} from './actions';
import { snakeToCamelCase } from './utils'
const initialState = {
isAuthenticated: false,
isLoggingIn: false,
isFetchingUserData: false,
isRegisteringUser: false,
isChargingMembership: false,
userToken: null,
user: null,
loginError: null,
userError: null,
registerError: null,
chargeError: null,
lastOrder: null,
};
function formatErrors(errs) {
let _errs = {};
for(let [key, value] of Object.entries(errs)) {
_errs[snakeToCamelCase(key)] = value.join('\n');
}
return _errs
}
export default function duskenApp(state = initialState, action) {
switch (action.type) {
/* Login */
case LOGIN_REQUEST:
return Object.assign({}, state, {
isLoggingIn: true,
@@ -42,9 +64,11 @@ export default function duskenApp(state = initialState, action) {
case LOGOUT:
return initialState;
/* User data */
case USER_DATA_REQUEST:
return Object.assign({}, state, {
isFetchingUserData: true
isFetchingUserData: true,
userError: null
});
case USER_DATA_FAILURE:
@@ -56,7 +80,51 @@ export default function duskenApp(state = initialState, action) {
case USER_DATA_SUCCESS:
return Object.assign({}, state, {
isFetchingUserData: false,
user: action.data
user: action.data,
});
/* Register user */
case REGISTER_USER_REQUEST:
return Object.assign({}, state, {
isRegisteringUser: true,
registerError: null,
});
case REGISTER_USER_FAILURE:
return Object.assign({}, state, {
isRegisteringUser: false,
registerError: formatErrors(action.registerError)
});
case REGISTER_USER_SUCCESS:
let userData = action.data;
const token = action.data.auth_token;
delete userData['auth_token'];
return Object.assign({}, state, {
isRegisteringUser: false,
isAuthenticated: true,
user: userData,
userToken: token,
});
/* User data */
case MEMBERSHIP_CHARGE_REQUEST:
return Object.assign({}, state, {
isChargingMembership: true,
chargeError: null
});
case MEMBERSHIP_CHARGE_FAILURE:
return Object.assign({}, state, {
isChargingMembership: false,
chargeError: action.chargeError
});
case MEMBERSHIP_CHARGE_SUCCESS:
return Object.assign({}, state, {
isChargingMembership: false,
lastOrder: action.data,
});
default:
+2 -1
View File
@@ -22,7 +22,8 @@ export default class LoginScreen extends Component {
render() {
return (
<StyleProvider style={getTheme()}><LoginContainer onLogin={this.onLogin} />
<StyleProvider style={getTheme()}>
<LoginContainer onLogin={this.onLogin} />
</StyleProvider>
)
}
+9 -2
View File
@@ -19,7 +19,7 @@ export default class MembershipScreen extends Component {
),
};
onLoginPress = () => {
loginNavigate = () => {
this.props.navigation.navigate('Login');
};
@@ -27,10 +27,17 @@ export default class MembershipScreen extends Component {
this.props.navigation.navigate('Membership');
};
registerNavigate = () => {
this.props.navigation.navigate('UserRegister');
};
render() {
return (
<StyleProvider style={getTheme()}>
<MembershipContainer onLoginPress={this.onLoginPress} logoutNavigate={this.logoutNavigate} />
<MembershipContainer
onLoginPress={this.loginNavigate}
onLogoutPress={this.logoutNavigate}
onRegisterPress={this.registerNavigate} />
</StyleProvider>
)
}
+30
View File
@@ -0,0 +1,30 @@
import React, { Component } from 'react';
import UserRegisterContainer from "../modules/register/UserRegisterContainer";
import {StyleProvider} from "native-base";
import getTheme from '../../native-base-theme/components';
export default class UserRegisterScreen extends Component {
static navigationOptions = {
title: 'Bli medlem',
headerStyle: {
backgroundColor: '#f58220',
},
headerTitleStyle: {
color: 'white',
},
headerTintColor: 'white',
};
onRegister = () => {
// This works since UserRegisterScreen is allways navigated to from MembershipScreen
this.props.navigation.goBack();
};
render() {
return (
<StyleProvider style={getTheme()}>
<UserRegisterContainer onRegister={this.onRegister} />
</StyleProvider>
)
}
}
+24
View File
@@ -0,0 +1,24 @@
import { Platform } from "react-native";
import platform from "native-base/src/theme/variables/platform";
const colors = {
danger: platform.btnDangerBg
};
const card = {
...Platform.select({
ios: {
shadowColor: 'rgba(0,0,0, .2)',
shadowOffset: {height: 0, width: 0},
shadowOpacity: 1,
shadowRadius: 1,
},
android: {
elevation: 1,
},
}),
padding: 8,
margin: 8,
backgroundColor: '#fff',
};
export default {card, colors};
+3
View File
@@ -0,0 +1,3 @@
export function snakeToCamelCase(val) {
return val.replace(/_([a-z])/g, function (g) { return g[1].toUpperCase(); });
}
+1284 -1219
View File
File diff suppressed because it is too large. Load diff