Skip to content

feat: 프로필 이미지 선택/닉네임 프로필 페이지 연동 #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/design-system/.storybook/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@
format('woff');
font-style: normal;
}

input {
outline: none;
}
12 changes: 8 additions & 4 deletions packages/design-system/src/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import React, { ReactNode } from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { TouchableOpacity } from 'react-native';

export type ButtonProps = {
onPress: () => void;
disabled: boolean;
children: ReactNode;
disabled?: boolean;
onPress?: () => void;
};

export const Button = ({ onPress, disabled, children }: ButtonProps) => {
export const Button = ({
onPress,
children,
disabled = false,
}: ButtonProps) => {
return (
<TouchableOpacity
className={`py-[11px] w-full rounded-[24px] justify-center items-center ${
Expand Down
28 changes: 22 additions & 6 deletions packages/design-system/src/Font.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,33 +28,49 @@ interface FontProps {
children: ReactNode;
type: FontType;
color: Color;
underline?: boolean;
}

const FontRegular = ({ type, color, children }: FontProps) => {
const FontRegular = ({ type, color, children, underline }: FontProps) => {
const fontType = FONT_TYPE_PREFIX[type];
const colorStyle = COLOR_PREFIX[color];
const underLineStyle = underline ? 'underline underline-offset-1' : '';
const fontStyle = FONT_PREFIX.MEDIUM;
return (
<Text className={`${fontStyle} ${fontType} ${colorStyle}`}>{children}</Text>
<Text
className={`${fontStyle} ${fontType} ${colorStyle} ${underLineStyle}`}
>
{children}
</Text>
);
};

const FontBold = ({ type, color, children }: FontProps) => {
const FontBold = ({ type, color, children, underline }: FontProps) => {
const fontType = FONT_TYPE_PREFIX[type];
const colorStyle = COLOR_PREFIX[color];
const underLineStyle = underline ? 'underline underline-offset-1' : '';
const fontStyle = FONT_PREFIX.BOLD;
return (
<Text className={`${fontStyle} ${fontType} ${colorStyle}`}>{children}</Text>
<Text
className={`${fontStyle} ${fontType} ${colorStyle} ${underLineStyle}`}
>
{children}
</Text>
);
};

const FontLight = ({ type, color, children }: FontProps) => {
const FontLight = ({ type, color, children, underline }: FontProps) => {
const fontType = FONT_TYPE_PREFIX[type];
const colorStyle = COLOR_PREFIX[color];
const underLineStyle = underline ? 'underline underline-offset-1' : '';
const fontStyle = FONT_PREFIX.LIGHT;

return (
<Text className={`${fontType} ${fontStyle} ${colorStyle}`}>{children}</Text>
<Text
className={`${fontType} ${fontStyle} ${colorStyle} ${underLineStyle}`}
>
{children}
</Text>
);
};

Expand Down
10 changes: 5 additions & 5 deletions packages/design-system/src/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@ interface TextField {
onChange: (value: string) => void;
onSubmit: () => void;
placeholder: string;
validate: () => boolean;
isCorrect: boolean;
}

export function TextField({
value,
onChange,
placeholder,
onSubmit,
validate,
isCorrect,
}: TextField) {
const defaultClassName =
'text-SPOT-black text-body2 rounded-md p-4 bg-SPOT-white/60';
'text-SPOT-black text-body2 rounded-md p-4 bg-SPOT-white/60 border-[2px] border-bg-SPOT-white/60';
const incorrectClassName = 'border-SPOT-red border-[2px]';
const correctClassName = 'border-Permission-green border-[2px]';

const getBorderClassName = () => {
if (typeof validate === 'function') {
return validate() ? correctClassName : incorrectClassName;
if (typeof isCorrect === 'boolean' && value !== '') {
return isCorrect ? correctClassName : incorrectClassName;
}

return 'border-none';
Expand Down
13 changes: 5 additions & 8 deletions packages/design-system/stories/TextField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@ import type { Meta, StoryObj } from '@storybook/react';
import { TextField } from '../src/TextField';
import { useState } from 'react';

const correct = () => true;
const incorrect = () => false;

const meta = {
title: 'Spots/TextField',
component: ({ placeholder, onSubmit, validate }) => {
component: ({ placeholder, onSubmit, isCorrect }) => {
const [inputValue, setInputValue] = useState('');
return (
<TextField
value={inputValue}
onChange={(text) => setInputValue(text)}
placeholder={placeholder}
onSubmit={onSubmit}
validate={validate}
isCorrect={isCorrect}
/>
);
},
Expand All @@ -25,8 +22,8 @@ const meta = {
tags: ['autodocs'],

argTypes: {
validate: {
options: [correct, incorrect],
isCorrect: {
options: [true, false],
control: 'radio',
},
},
Expand All @@ -42,6 +39,6 @@ export const defaultStory: Story = {
onChange: () => {},
placeholder: 'SPOT-Placeholder',
onSubmit: () => {},
validate: () => true,
isCorrect: true,
},
};
25 changes: 25 additions & 0 deletions packages/react-native/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,27 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-image-picker (7.1.2):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.01.01.00)
- RCTRequired
- RCTTypeSafety
- React-Codegen
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-safe-area-context (4.10.8):
- React-Core
- react-native-view-shot (3.8.0):
Expand Down Expand Up @@ -1282,6 +1303,7 @@ DEPENDENCIES:
- React-logger (from `../../../node_modules/react-native/ReactCommon/logger`)
- React-Mapbuffer (from `../../../node_modules/react-native/ReactCommon`)
- "react-native-cameraroll (from `../../../node_modules/@react-native-camera-roll/camera-roll`)"
- react-native-image-picker (from `../../../node_modules/react-native-image-picker`)
- react-native-safe-area-context (from `../../../node_modules/react-native-safe-area-context`)
- react-native-view-shot (from `../../../node_modules/react-native-view-shot`)
- React-nativeconfig (from `../../../node_modules/react-native/ReactCommon`)
Expand Down Expand Up @@ -1384,6 +1406,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native/ReactCommon"
react-native-cameraroll:
:path: "../../../node_modules/@react-native-camera-roll/camera-roll"
react-native-image-picker:
:path: "../../../node_modules/react-native-image-picker"
react-native-safe-area-context:
:path: "../../../node_modules/react-native-safe-area-context"
react-native-view-shot:
Expand Down Expand Up @@ -1480,6 +1504,7 @@ SPEC CHECKSUMS:
React-logger: fa92ba4d3a5d39ac450f59be2a3cec7b099f0304
React-Mapbuffer: 9f68550e7c6839d01411ac8896aea5c868eff63a
react-native-cameraroll: a9138c165c9975da773d26945591d313992c799b
react-native-image-picker: c3afe5472ef870d98a4b28415fc0b928161ee5f7
react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371
react-native-view-shot: 6b7ed61d77d88580fed10954d45fad0eb2d47688
React-nativeconfig: fa5de9d8f4dbd5917358f8ad3ad1e08762f01dcb
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"react": "18.2.0",
"react-native": "0.74.3",
"react-native-gesture-handler": "^2.17.1",
"react-native-image-picker": "^7.1.2",
"react-native-linear-gradient": "^2.8.3",
"react-native-permissions": "^4.1.5",
"react-native-safe-area-context": "^4.10.8",
Expand Down
21 changes: 21 additions & 0 deletions packages/react-native/src/assets/SelectProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Circle, Path, Svg, SvgProps } from 'react-native-svg';

export default function SelectProfile({ width, height, color }: SvgProps) {
return (
<Svg
width={width || '140'}
height={height || '140'}
viewBox="0 0 140 140"
fill="none"
>
<Circle opacity="0.6" cx="70" cy="70" r="70" fill={color || 'white'} />
<Path
opacity="0.7"
fill-rule="evenodd"
clip-rule="evenodd"
d="M71.5 60C71.5 59.1716 70.8284 58.5 70 58.5C69.1716 58.5 68.5 59.1716 68.5 60V68.3965H60C59.1716 68.3965 58.5 69.0681 58.5 69.8965C58.5 70.725 59.1716 71.3965 60 71.3965H68.5V80C68.5 80.8284 69.1716 81.5 70 81.5C70.8284 81.5 71.5 80.8284 71.5 80V71.3965H80C80.8284 71.3965 81.5 70.725 81.5 69.8965C81.5 69.0681 80.8284 68.3965 80 68.3965H71.5V60Z"
fill={color || 'white'}
/>
</Svg>
);
}
22 changes: 20 additions & 2 deletions packages/react-native/src/hooks/useGallery.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CameraRoll } from '@react-native-camera-roll/camera-roll';
import { Alert, Linking, Platform } from 'react-native';
import { launchImageLibrary } from 'react-native-image-picker';
import {
check,
PERMISSIONS,
Expand All @@ -11,7 +12,8 @@ import {
export default function useGallery() {
const checkGalleryPermission = (result: PermissionStatus) => {
switch (result) {
case (RESULTS.GRANTED, RESULTS.LIMITED):
case RESULTS.GRANTED:
case RESULTS.LIMITED:
return 'granted';

default:
Expand Down Expand Up @@ -55,5 +57,21 @@ export default function useGallery() {
return CameraRoll.saveAsset(uri);
};

return { savePhoto };
const getPhoto = async () => {
const hasPermission = await hasGalleryPermission();
if (!hasPermission) return Promise.reject();

const response = await launchImageLibrary({
mediaType: 'photo',
selectionLimit: 1,
});

if (response.didCancel) {
return null;
}

return response.assets?.[0]?.uri;
};

return { savePhoto, getPhoto };
}
28 changes: 17 additions & 11 deletions packages/react-native/src/pages/Login/Nickname.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Font } from 'design-system';
import { Font, TextField } from 'design-system';
import { useState } from 'react';
import { TextInput, View } from 'react-native';
import { View } from 'react-native';
import Header from '@/components/signup/Header';
import Overlay from '@/components/signup/Overlay';
import { SignupStackNavigation } from '@/types/navigation';
Expand All @@ -12,6 +12,16 @@ interface NicknameProps {
export default function Niakname({ navigation }: NicknameProps) {
const [nickname, setNickname] = useState('');

const isCorrect = nickname.length > 0 && nickname.length < 7;

const handleSubmit = () => {
if (isCorrect) {
navigation.navigate('Signup/Profile', {
nickname,
});
}
};

return (
<Overlay>
<Header
Expand All @@ -30,17 +40,13 @@ export default function Niakname({ navigation }: NicknameProps) {
<Font type="body2" color="white">
닉네임
</Font>
<TextInput

<TextField
value={nickname}
onChangeText={(newNickname) => setNickname(newNickname)}
onChange={(newNickname) => setNickname(newNickname)}
onSubmit={handleSubmit}
placeholder="닉네임"
placeholderTextColor="#ffffff"
className="text-SPOT-white text-body2 rounded-md p-4 placeholder-SPOT-white bg-SPOT-white/60 mt-[8px]"
onSubmitEditing={() => {
navigation.navigate('Signup/Profile', {
nickname,
});
}}
isCorrect={isCorrect}
/>
</View>
</Overlay>
Expand Down
30 changes: 30 additions & 0 deletions packages/react-native/src/pages/Login/NicknameProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Font } from 'design-system';
import { View } from 'react-native';
import Header from '@/components/signup/Header';
import Overlay from '@/components/signup/Overlay';
import { SignupStackNavigation } from '@/types/navigation';

interface NicknameProfileProps {
navigation: SignupStackNavigation<'Signup/NicknameProfile'>;
}

export default function NicknameProfile({ navigation }: NicknameProfileProps) {
return (
<Overlay>
<View>
<Header
onBack={() => navigation.goBack()}
onCancel={() => navigation.goBack()}
/>
<View className="flex w-full mt-[30px]">
<Font type="mainTitle" color="white">
배경 색상을
</Font>
<Font type="mainTitle" color="white">
선택하세요
</Font>
</View>
</View>
</Overlay>
);
}
Loading
Loading