Skip to content

Commit

Permalink
Feature/feedback page (#47)
Browse files Browse the repository at this point in the history
* [enhancement]: add placeholder color and type

* [feat] disable default header, use header in page

* Feedback banner svg component

* FeedBack page initial markup and styles

* feat: inital modal setup

* [chore]: confetti asset

* add extra styles and types
- feedback input border color
- feedback header background color

* add Feedback Banner Svg

* [feat]: add feedback button component

* [feat]: Feedback Rating button test

* [feat]: add Feedback page component

* [feat]: add feedback modal.

* [fix] move submit button to component

* [fix] move back button to component

* [feat] add onSelected styling

* [fix] move pressables to  components

* [fix] use correct padding size

* [fix] use corrct text size

* [fix] use space component to add spacing

* [fix] add onSelected prop

* [chore] style cleanup and refactor

* [feat] add test for button components
- BackNavigationButton
- SubmitFeedbackButton

* [refactor] add onselected prop

* [fix] use expo image component

* [fix] change styling to camelcase

* [fix] use stack screen header component

* [feat] add modal tint

* [feat] add Banner Images

* [fix] use image background component

* [fix] improve styling and add image properties

* [chore]

* fixed feedback page imagebackground

---------

Co-authored-by: Tony Kharioki <antolisah@gmail.com>
  • Loading branch information
gmwaniki and kharioki authored Oct 4, 2023
1 parent 416eedd commit 13c1db4
Show file tree
Hide file tree
Showing 15 changed files with 461 additions and 22 deletions.
18 changes: 18 additions & 0 deletions __tests__/components/buttons/BackNavigationButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { fireEvent, render, screen } from '@testing-library/react-native';
import React from 'react';

import BackNavigationButton from '../../../components/buttons/BackNavigationButton';

describe('<BackNavigationButton/>', () => {
it('Renders back navigation button component', () => {
render(<BackNavigationButton text="Go back" onPress={() => console.log('back')} />);
expect(screen.getByText('Go back')).toBeDefined();
});
it('calls the function provided by onPress prop after pressing the button', () => {
const onPress = jest.fn();
render(<BackNavigationButton text="Go back" onPress={onPress} />);
fireEvent.press(screen.getByText('Go back'));

expect(onPress).toHaveBeenCalledTimes(1);
});
});
26 changes: 26 additions & 0 deletions __tests__/components/buttons/FeedbackRatingButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { fireEvent, render, screen } from '@testing-library/react-native';
import React from 'react';

import FeedBackRatingButton from '../../../components/buttons/FeedBackRatingButton';

describe('<FeedbackRatingButton/>', () => {
it('Renders feedback rating button component', () => {
render(
<FeedBackRatingButton
rating={{ icon: '', text: 'Ok', value: 0 }}
onPress={() => console.log('Feedback gotten succesfully')}
testID="btn1"
onSelected={true}
/>,
);
expect(screen.getByText('Ok')).toBeDefined();
});

it('calls the function provided by onPress prop after pressing the button', () => {
const onPress = jest.fn();
render(<FeedBackRatingButton rating={{ icon: '', text: 'Ok', value: 0 }} onPress={onPress} onSelected={true} />);
fireEvent.press(screen.getByText('Ok'));

expect(onPress).toHaveBeenCalledTimes(1);
});
});
25 changes: 25 additions & 0 deletions __tests__/components/buttons/SubmitFeedBackButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { fireEvent, render, screen } from '@testing-library/react-native';
import React from 'react';

import SubmitFeedbackButton from '../../../components/buttons/SubmitFeedbackButton';

describe('<SubmitFeedbackButton/>', () => {
it('Renders submit feedback button component', () => {
render(
<SubmitFeedbackButton
openModal={() => {
console.log('hello');
}}
text="SUBMIT FEEDBACK"
/>,
);
expect(screen.getByText('SUBMIT FEEDBACK')).toBeDefined();
});
it('calls the function provided by onPress prop after pressing the button', () => {
const onPress = jest.fn();
render(<SubmitFeedbackButton openModal={onPress} text="SUBMIT FEEDBACK" />);
fireEvent.press(screen.getByText('SUBMIT FEEDBACK'));

expect(onPress).toHaveBeenCalledTimes(1);
});
});
8 changes: 5 additions & 3 deletions app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ export default () => {
<Stack.Screen
name="feedback"
options={{
headerTintColor: colors.text,
headerStyle: {
backgroundColor: colors.background,
headerTintColor: colors.whiteConstant,
title: 'Feedback',
headerTransparent: true,
headerTitleStyle: {
fontWeight: 'normal',
},
}}
/>
Expand Down
135 changes: 125 additions & 10 deletions app/(app)/feedback.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,135 @@
import React from 'react';
import { useTheme } from '@react-navigation/native';
import React, { useState } from 'react';
import { ImageBackground, StyleSheet, View } from 'react-native';
import { TextInput } from 'react-native-gesture-handler';
import FeedBackRatingButton from '../../components/buttons/FeedBackRatingButton';
import SubmitFeedbackButton from '../../components/buttons/SubmitFeedbackButton';
import Space from '../../components/common/Space';
import StyledText from '../../components/common/StyledText';
import MainContainer from '../../components/container/MainContainer';
import FeedbackSentModal from '../../components/modals/FeedbackSentModal';
import { typography } from '../../config/typography';

// TODO: implement feedback page
/**
* TASKS:
* Implement feedback page
* Should include a reactions component, a textarea and button
*/
export type TypeRatingStates = {
icon: string;
text: string;
value: number;
};

const Feedback = () => {
const { colors, dark } = useTheme();
const [showModal, setShowModal] = useState(false);
const [selectedRating, setSelectedRating] = useState(2);
const [description, setDescription] = useState('');
const ratingStates: Array<TypeRatingStates> = [
{ icon: '😔', text: 'Bad', value: 0 },
{ icon: '😐', text: 'Okay', value: 1 },
{ icon: '😊', text: 'Great', value: 2 },
];
const openModal = () => {
setShowModal(true);
};

const feedback = () => {
return (
<MainContainer preset="scroll">
<StyledText>feedback</StyledText>
<View style={styles.main}>
<ImageBackground
source={dark ? require('../../assets/images/bannerDark.png') : require('../../assets/images/bannerLight.png')}
style={styles.feedBackBanner}
resizeMode="cover"
/>
<Space size={10} />
<View style={styles.feedBackFormContainer}>
<StyledText
size="lg"
font="bold"
variant="text"
style={[styles.FeedBackFormTitle, { color: colors.primary }]}
>
Your feedback helps us improve
</StyledText>
<Space size={29} />
<View style={[styles.feedBackForm, { backgroundColor: colors.background, borderColor: colors.border }]}>
<StyledText style={styles.feedBackFormLabel} size="base">
How is/was the event
</StyledText>
<View style={styles.feedBackFormRatingContainer}>
{ratingStates.map((rating, index) => {
return (
<FeedBackRatingButton
rating={rating}
key={index}
onPress={setSelectedRating}
onSelected={rating.value === selectedRating}
/>
);
})}
</View>
</View>
<Space size={30} />
<TextInput
style={[
styles.feedbackInput,
{ backgroundColor: colors.bg, borderColor: colors.borderColor, color: colors.bgInverse },
]}
placeholder="Type message here"
placeholderTextColor={colors.placeHolder}
value={description}
onChangeText={setDescription}
/>
<Space size={26} />
<SubmitFeedbackButton openModal={openModal} text="SUBMIT FEEDBACK" />
</View>

<FeedbackSentModal showModal={showModal} />
</View>
</MainContainer>
);
};

export default feedback;
const styles = StyleSheet.create({
main: {
flex: 1,
width: '100%',
},
feedBackBanner: {
height: 179,
flex: 1,
width: '100%',
},
feedBackForm: {
flex: 1,
paddingTop: 17,
borderRadius: 10,
borderWidth: 1,
},
feedBackFormContainer: {
flex: 1,
paddingVertical: 10,
paddingHorizontal: 16,
},
feedBackFormLabel: {
textAlign: 'center',
paddingBottom: 30,
},
feedBackFormRatingContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-evenly',
paddingBottom: 27,
},
FeedBackFormTitle: {
textAlign: 'center',
},
feedbackInput: {
paddingLeft: 20,
paddingTop: 12,
minHeight: 115,
textAlignVertical: 'top',
borderRadius: 7,
borderWidth: 1,
fontFamily: typography.primary.light,
},
});

export default Feedback;
71 changes: 71 additions & 0 deletions assets/artworks/FeedBackBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useTheme } from '@react-navigation/native';
import React from 'react';
import { StyleSheet, View } from 'react-native';
import type { SvgProps } from 'react-native-svg';
import Svg, { ClipPath, Defs, G, Path } from 'react-native-svg';

export interface ISvgProps extends SvgProps {
xmlns?: string;
xmlnsXlink?: string;
xmlSpace?: string;
}

const FeedBackBanner = (props: ISvgProps) => {
const { colors } = useTheme();
const originalWidth = 412;
const originalHeight = 179;
const aspectRatio = originalWidth / originalHeight;
return (
<View style={styles(aspectRatio).svgContainer}>
<Svg
data-name="Group 384"
width="100%"
height="100%"
viewBox={`0 0 ${originalWidth} ${originalHeight}`}
{...props}
>
<Defs>
<ClipPath id="a">
<Path
data-name="Rectangle 693"
transform="translate(232 -402)"
fill={colors.assetAccent}
d="M0 0H412V179H0z"
/>
</ClipPath>
</Defs>
<G data-name="Group 383">
<G data-name="Group 382">
<Path data-name="Rectangle 691" fill={colors.assetAccent} d="M0 0H412V179H0z" />
<G data-name="Mask Group 1" transform="translate(-232 402)" clipPath="url(#a)">
<G transform="translate(338.688 -465.155)">
<Path
data-name="Rectangle 692"
transform="rotate(45 -140.229 182.44)"
fill="#ff6e4d"
d="M0 0H91.444V124.36H0z"
/>
<Path
data-name="Path 163"
d="M217.194 152.59l87.938-87.938L240.479 0l-87.938 87.938L64.733.129.08 64.782 240.592 305.31l64.669-64.669z"
transform="translate(.051)"
fill="#00e2c3"
/>
</G>
</G>
</G>
</G>
</Svg>
</View>
);
};
const styles = (aspectRatio: number) => {
return StyleSheet.create({
svgContainer: {
width: '100%',
aspectRatio,
},
});
};

export default FeedBackBanner;
Binary file added assets/images/bannerDark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/bannerLight.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/confetti.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions components/buttons/BackNavigationButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { AntDesign } from '@expo/vector-icons';
import { useTheme } from '@react-navigation/native';
import React from 'react';
import { Pressable, StyleSheet } from 'react-native';
import StyledText from '../common/StyledText';

type BackNavigationButtonProps = {
text: string;
onPress: () => void;
};

const BackNavigationButton = ({ text, onPress }: BackNavigationButtonProps) => {
const { colors } = useTheme();
return (
<Pressable style={styles.headerTitle} onPress={onPress}>
<AntDesign name="arrowleft" size={24} color={'white'} />
<StyledText style={[{ color: colors.whiteConstant }]} size="lg">
{text}
</StyledText>
</Pressable>
);
};

const styles = StyleSheet.create({
headerTitle: {
paddingLeft: 23,
flexDirection: 'row',
columnGap: 22,
position: 'absolute',
width: '100%',
paddingTop: 26,
paddingBottom: 20,
},
});

export default BackNavigationButton;
48 changes: 48 additions & 0 deletions components/buttons/FeedBackRatingButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useTheme } from '@react-navigation/native';
import React from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';
import type { TypeRatingStates } from '../../app/(app)/feedback';
import StyledText from '../common/StyledText';

type Props = {
onPress: (value: number) => void;
rating: TypeRatingStates;
onSelected: boolean;
testID?: string;
};

const FeedBackRatingButton = ({ onPress, rating, onSelected }: Props) => {
const { colors } = useTheme();
const { icon, text, value } = rating;

return (
<Pressable
style={[
styles.pressableEmoji,
{ backgroundColor: colors.bg, borderColor: colors.bg },
onSelected && { borderColor: colors.primary },
]}
onPress={() => onPress(value)}
>
<Text style={styles.formRatingText}>{icon}</Text>
<StyledText font="bold" size="sm">
{text}
</StyledText>
</Pressable>
);
};
const styles = StyleSheet.create({
formRatingText: {
fontSize: 30,
},
pressableEmoji: {
minWidth: 67,
minHeight: 67,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 4,
borderWidth: 1,
},
});

export default FeedBackRatingButton;
Loading

0 comments on commit 13c1db4

Please sign in to comment.