diff --git a/.vscode/settings.json b/.vscode/settings.json index c9cd64d..ab13ead 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -44,6 +44,7 @@ "lotties", "Onesignal", "Pressable", - "Pretendard" + "Pretendard", + "svgs" ] } diff --git a/app/(app)/(tabs)/profile.tsx b/app/(app)/(tabs)/profile.tsx index e631f79..8efd542 100644 --- a/app/(app)/(tabs)/profile.tsx +++ b/app/(app)/(tabs)/profile.tsx @@ -9,6 +9,7 @@ import {css} from '@emotion/native'; import {Pressable} from 'react-native'; import {IC_ICON} from '../../../src/icons'; import {openURL} from '../../../src/utils/common'; +import DoobooStats from '../../../src/components/fragments/DoobooStats'; const Container = styled.SafeAreaView` flex: 1; @@ -42,13 +43,20 @@ const UserName = styled(Typography.Heading5)` margin-bottom: 8px; `; -const UserBio = styled.Text` +const UserAffiliation = styled.Text` font-size: 16px; color: ${({theme}) => theme.role.secondary}; text-align: center; margin-bottom: 16px; `; +const UserBio = styled.Text` + font-size: 16px; + color: ${({theme}) => theme.text.label}; + text-align: center; + margin-bottom: 16px; +`; + const InfoCard = styled.View` background-color: ${({theme}) => theme.bg.paper}; border-radius: 15px; @@ -72,9 +80,7 @@ const InfoLabel = styled(Typography.Body2)` font-family: Pretendard-Bold; `; -const InfoValue = styled(Typography.Body2)` - flex: 1; -`; +const InfoValue = styled(Typography.Body2)``; const TagContainer = styled.View` flex-direction: row; @@ -114,6 +120,9 @@ export default function Profile(): JSX.Element { source={user?.avatar_url ? {uri: user?.avatar_url} : IC_ICON} /> {user?.display_name || ''} + {user?.affiliation ? ( + {user?.affiliation} + ) : null} {user?.introduction ? {user?.introduction} : null} @@ -134,10 +143,7 @@ export default function Profile(): JSX.Element { {user?.github_id || ''} - - - {t('onboarding.affiliation')} - {user?.affiliation || ''} + diff --git a/app/(app)/post/[id]/replies.tsx b/app/(app)/post/[id]/replies.tsx index d4eca66..7539288 100644 --- a/app/(app)/post/[id]/replies.tsx +++ b/app/(app)/post/[id]/replies.tsx @@ -97,8 +97,6 @@ export default function Replies({ })), }); - console.log('newReply', newReply); - if (newReply) { setReplies((prevReplies) => [newReply, ...prevReplies]); } diff --git a/app/(auth)/sign-in.tsx b/app/(auth)/sign-in.tsx index 5bb19b0..fc82cf9 100644 --- a/app/(auth)/sign-in.tsx +++ b/app/(auth)/sign-in.tsx @@ -11,7 +11,7 @@ import {Redirect, Stack, useRouter} from 'expo-router'; import {useRecoilValue} from 'recoil'; import {googleClientIdIOS, googleClientIdWeb} from '../../config'; -import {IC_CROSSPLATFORMS, IC_GOOGLE, IC_ICON} from '../../src/icons'; +import {IMG_CROSSPLATFORMS, IC_GOOGLE, IC_ICON} from '../../src/icons'; import {authRecoilState} from '../../src/recoil/atoms'; import {t} from '../../src/STRINGS'; import {supabase} from '../../src/supabase'; @@ -162,7 +162,7 @@ export default function SignIn(): JSX.Element { {t('signIn.description')} => { + try { + const response = await fetch(API_ENDPOINT, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({login}), + }); + + if (!response.ok) { + throw new Error('HTTP error! status:' + response.status); + } + + return await response.json(); + } catch (error) { + if (__DEV__) console.error('Error fetching data:', error); + throw new Error(t('error.failedToFetchData')); + } +}; diff --git a/src/components/fragments/DoobooStats.tsx b/src/components/fragments/DoobooStats.tsx new file mode 100644 index 0000000..e782f13 --- /dev/null +++ b/src/components/fragments/DoobooStats.tsx @@ -0,0 +1,60 @@ +import styled, {css} from '@emotion/native'; +import {useEffect, useState} from 'react'; +import {DoobooGithubStats} from '../../types/github-stats'; +import {updateDoobooGithub} from '../../apis/githubStatsQueries'; +import Scouter from '../uis/Scouter'; +import {User} from '../../types'; +import CustomLoadingIndicator from '../uis/CustomLoadingIndicator'; + +const Container = styled.View``; + +type Props = { + user: User | null; +}; + +export default function DoobooStats({user}: Props): JSX.Element | null { + const [doobooStats, setDoobooStats] = useState( + null, + ); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchGithubStats = async () => { + try { + if (!user?.github_id) { + return; + } + const result = await updateDoobooGithub(user!.github_id!); + + if (!!result?.stats) { + setDoobooStats(result.stats); + } + } catch (e: any) { + setError(e.message); + } + }; + + if (!!user?.github_id) { + fetchGithubStats(); + } + }, [user, user?.github_id]); + + if (error) { + return null; + } + + return ( + + {doobooStats ? ( + + ) : ( + + )} + + ); +} diff --git a/src/components/svgs/SvgStatsDooboo.tsx b/src/components/svgs/SvgStatsDooboo.tsx new file mode 100644 index 0000000..bd67b40 --- /dev/null +++ b/src/components/svgs/SvgStatsDooboo.tsx @@ -0,0 +1,31 @@ +import { useDooboo } from 'dooboo-ui'; +import {Svg, G, Path, Defs, ClipPath, Rect} from 'react-native-svg'; + +type Props = { + color?: string; +}; + +export default function SvgStatsDooboo({color}: Props) { + const {theme} = useDooboo(); + const fill = color || theme.text.basic; + + return ( + + + + + + + + + + + + ); +} diff --git a/src/components/svgs/SvgStatsEarth.tsx b/src/components/svgs/SvgStatsEarth.tsx new file mode 100644 index 0000000..fec734d --- /dev/null +++ b/src/components/svgs/SvgStatsEarth.tsx @@ -0,0 +1,30 @@ +import {useDooboo} from 'dooboo-ui'; +import {G, Path, Svg} from 'react-native-svg'; + +type Props = { + color?: string; +}; + +export default function SvgStatsEarth({color}: Props) { + const {theme} = useDooboo(); + const fill = color || theme.text.basic; + + return ( + + + + + + + + ); +} diff --git a/src/components/svgs/SvgStatsFire.tsx b/src/components/svgs/SvgStatsFire.tsx new file mode 100644 index 0000000..e34527b --- /dev/null +++ b/src/components/svgs/SvgStatsFire.tsx @@ -0,0 +1,30 @@ +import {useDooboo} from 'dooboo-ui'; +import {G, Path, Svg} from 'react-native-svg'; + +type Props = { + color?: string; +}; + +export default function SvgStatsFire({color}: Props) { + const {theme} = useDooboo(); + const fill = color || theme.text.basic; + + return ( + + + + + + + + ); +} diff --git a/src/components/svgs/SvgStatsGold.tsx b/src/components/svgs/SvgStatsGold.tsx new file mode 100644 index 0000000..1eeed17 --- /dev/null +++ b/src/components/svgs/SvgStatsGold.tsx @@ -0,0 +1,30 @@ +import {useDooboo} from 'dooboo-ui'; +import {G, Path, Svg} from 'react-native-svg'; + +type Props = { + color?: string; +}; + +export default function SvgStatsGold({color}: Props) { + const {theme} = useDooboo(); + const fill = color || theme.text.basic; + + return ( + + + + + + + + ); +} diff --git a/src/components/svgs/SvgStatsPerson.tsx b/src/components/svgs/SvgStatsPerson.tsx new file mode 100644 index 0000000..a7de333 --- /dev/null +++ b/src/components/svgs/SvgStatsPerson.tsx @@ -0,0 +1,34 @@ +import {useDooboo} from 'dooboo-ui'; +import {G, Path, Svg} from 'react-native-svg'; + +type Props = { + color?: string; +}; + +export default function SvgStatsPerson({color}: Props) { + const {theme} = useDooboo(); + const fill = color || theme.text.basic; + + return ( + + + + + + + + + ); +} diff --git a/src/components/svgs/SvgStatsTree.tsx b/src/components/svgs/SvgStatsTree.tsx new file mode 100644 index 0000000..9c9819e --- /dev/null +++ b/src/components/svgs/SvgStatsTree.tsx @@ -0,0 +1,23 @@ +import {useDooboo} from 'dooboo-ui'; +import {G, Path, Svg} from 'react-native-svg'; + +type Props = { + color?: string; +}; + +export default function SvgStatsTree({color}: Props) { + const {theme} = useDooboo(); + const fill = color || theme.text.basic; + + return ( + + + + + + + ); +} diff --git a/src/components/svgs/SvgStatsWater.tsx b/src/components/svgs/SvgStatsWater.tsx new file mode 100644 index 0000000..322b674 --- /dev/null +++ b/src/components/svgs/SvgStatsWater.tsx @@ -0,0 +1,30 @@ +import {useDooboo} from 'dooboo-ui'; +import {G, Path, Svg} from 'react-native-svg'; + +type Props = { + color?: string; +}; + +export default function SvgStatsWater({color}: Props) { + const {theme} = useDooboo(); + const fill = color || theme.text.basic; + + return ( + + + + + + + + ); +} diff --git a/src/components/uis/Scouter/CombatDetails.tsx b/src/components/uis/Scouter/CombatDetails.tsx new file mode 100644 index 0000000..a9db49e --- /dev/null +++ b/src/components/uis/Scouter/CombatDetails.tsx @@ -0,0 +1,518 @@ +import styled from '@emotion/native'; +import {Typography, useDooboo} from 'dooboo-ui'; +import {type ReactElement} from 'react'; +import { + Linking, + Platform, + Text, + TouchableOpacity, + View, + ViewStyle, +} from 'react-native'; + +import { + ContentDetailDescProps, + DoobooGithubStats, + PluginStats, + Stats, + StatsDetail, + StatsElement, + StatType, +} from '../../../types/github-stats'; +import {t} from '../../../STRINGS'; +import SvgStatsDooboo from '../../svgs/SvgStatsDooboo'; +import SvgStatsTree from '../../svgs/SvgStatsTree'; +import SvgStatsEarth from '../../svgs/SvgStatsEarth'; +import SvgStatsFire from '../../svgs/SvgStatsFire'; +import SvgStatsGold from '../../svgs/SvgStatsGold'; +import SvgStatsWater from '../../svgs/SvgStatsWater'; +import SvgStatsPerson from '../../svgs/SvgStatsPerson'; + +const Container = styled.View` + width: ${Platform.OS === 'web' ? 'calc(100vw - 56px)' : undefined}; + min-width: 300px; + border-radius: 4px; + border-width: 1px; + border-color: ${({theme}) => theme.role.border}; + align-self: stretch; + justify-content: center; + overflow: hidden; + + flex-direction: column; + align-items: center; +`; + +const Details = styled.View` + width: 100%; + padding: 20px; +`; + +const DetailHead = styled.View` + margin-bottom: 12px; + + flex-direction: row; + align-items: center; +`; + +const DetailBody = styled.View` + width: 100%; + + flex-direction: column; +`; + +const StatIcons = ({ + selectedStats, + onPressStat, +}: { + selectedStats: StatType | null; + onPressStat: (stat: StatType | null) => void; +}): ReactElement => { + const {theme} = useDooboo(); + + const style: ViewStyle = { + width: 24, + height: 24, + }; + + return ( + + onPressStat(null)} + style={[style, {padding: 2}]} + > + + + onPressStat('tree')} + style={[style, {padding: 2}]} + > + + + onPressStat('fire')} + style={[style, {padding: 2}]} + > + + + onPressStat('earth')} + style={[style, {padding: 2}]} + > + + + onPressStat('gold')} + style={[style, {padding: 2}]} + > + + + onPressStat('water')} + style={[style, {padding: 2}]} + > + + + onPressStat('people')} + style={[style, {padding: 2}]} + > + + + + ); +}; + +const ContentDetailDescription = ({ + json, + stats, + selectedStats, +}: ContentDetailDescProps): ReactElement | null => { + const {theme} = useDooboo(); + + if (!stats) { + return null; + } + + const {name, description, statElements, score} = stats; + + const renderCommonDetail = ({ + statsElement, + statsDetails, + }: { + statsElement: StatsElement; + statsDetails?: ReactElement; + }): ReactElement => { + return ( + + + + + {statsElement.name} + + + + + {statsElement.totalCount?.toLocaleString() || ''} + + + + {statsDetails} + + ); + }; + + const renderDetails = ( + details: StatsDetail[], + statsElement: StatsElement, + ) => { + switch (details.length) { + case 0: + return renderCommonDetail({statsElement}); + case 1: + return renderCommonDetail({ + statsElement, + statsDetails: ( + + {details.map((detail: StatsDetail, i) => { + switch (detail.type) { + case 'repository': + return ( + + Linking.openURL(detail.url)} + > + {detail.name} + + {i < details.length - 1 ? , : null} + + ); + case 'commit': + return ( + + + + Linking.openURL( + `https://github.com/${detail.name}`, + ) + } + > + {detail.name} + + + + + Linking.openURL( + `https://github.com/${detail.name}/commit/${detail.sha}`, + ) + } + > + {`${detail.message}`} + + + + ); + case 'language': + return ( + + + + {detail.name} + + + + + {detail.count.toLocaleString()} + + + + ); + } + })} + + ), + }); + default: // Multiple details in one attr + return renderCommonDetail({ + statsElement, + statsDetails: ( + + {details.map((detail: StatsDetail, i) => { + switch (detail.type) { + case 'repository': + return ( + + Linking.openURL(detail.url)} + > + {detail.name} + + {i < details.length - 1 ? , : null} + + ); + case 'commit': + return ( + + + + Linking.openURL( + `https://github.com/${detail.name}`, + ) + } + > + {detail.name} + + + Linking.openURL( + `https://github.com/${detail.name}/commit/${detail.sha}`, + ) + } + > + {`${detail.message}`} + + + + ); + case 'language': + return ( + + + + {detail.name} + + + + + {detail.count.toLocaleString()} + + + + ); + } + })} + + ), + }); + } + }; + + if (!selectedStats) { + return ( + + + + {t('common.bio')} + + + + + {json.bio} + + + + ); + } + + return ( + + + + {name}{' '} + + ({t('common.score')}: {Math.round(score * 100)}) + + + + {description ? ( + + {description} + + ) : null} + + {statElements?.map((el: StatsElement) => { + if (!el.name) { + return null; + } + + const details: StatsDetail[] = el.details + ? JSON.parse(el.details) + : []; + + return {renderDetails(details, el)}; + })} + + + ); +}; + +export type Props = { + json: DoobooGithubStats['json']; + pluginStats: PluginStats; + selectedStat?: StatType | null; + onPressStat: (type: StatType | null) => void; +}; + +const CombatDetails = ({ + selectedStat = 'tree', + pluginStats, + onPressStat, + json, +}: Props): ReactElement => { + const stats: Stats = + selectedStat === 'tree' + ? pluginStats.tree + : selectedStat === 'fire' + ? pluginStats.fire + : selectedStat === 'earth' + ? pluginStats.earth + : selectedStat === 'gold' + ? pluginStats.gold + : selectedStat === 'water' + ? pluginStats.water + : pluginStats.people; + + return ( + + +
+ +
+
+ ); +}; + +export default CombatDetails; diff --git a/src/components/uis/Scouter/Score.tsx b/src/components/uis/Scouter/Score.tsx new file mode 100644 index 0000000..1893041 --- /dev/null +++ b/src/components/uis/Scouter/Score.tsx @@ -0,0 +1,101 @@ +import styled from '@emotion/native'; +import {type ReactElement} from 'react'; +import { + IC_TIER_BRONZE, + IC_TIER_CHALLENGER, + IC_TIER_DIAMOND, + IC_TIER_GOLD, + IC_TIER_MASTER, + IC_TIER_PLATINUM, + IC_TIER_SILVER, +} from '../../../icons'; + +const Container = styled.View` + align-self: stretch; + justify-content: center; + + flex-direction: row; + align-items: center; +`; + +const TierView = styled.View` + min-width: 90px; + + flex-direction: row; + align-items: center; +`; + +const ScoreView = styled.View` + min-width: 64px; + margin-left: 100px; + + flex-direction: row; + align-items: center; +`; + +const StyledImage = styled.Image` + width: 32px; + height: 32px; + margin-right: 8px; +`; + +const LabelText = styled.Text` + font-size: 12px; + opacity: 0.5; + color: ${({theme}) => theme.text.basic}; +`; + +const ValueText = styled.Text` + margin-left: 8px; + font-size: 24px; + font-weight: bold; + color: ${({theme}) => theme.text.basic}; +`; + +export type ScoreType = { + tierName: + | 'Iron' + | 'Bronze' + | 'Silver' + | 'Gold' + | 'Platinum' + | 'Master' + | 'Diamond' + | 'Challenger'; + score: number; +}; + +const Score = ({tierName, score = 0}: ScoreType): ReactElement => { + return ( + + + + {tierName} + + + AVG + {score} + + + ); +}; + +export default Score; diff --git a/src/components/uis/Scouter/StatsChart.tsx b/src/components/uis/Scouter/StatsChart.tsx new file mode 100644 index 0000000..c6f6aba --- /dev/null +++ b/src/components/uis/Scouter/StatsChart.tsx @@ -0,0 +1,269 @@ +import Svg, {Defs, LinearGradient, Polygon, Stop} from 'react-native-svg'; +import {IMG_SPIDER_WEB_LIGHT, IMG_SPIDER_WEB_DARK} from '../../../icons'; +import Animated, {BounceIn} from 'react-native-reanimated'; + +import {type ReactElement} from 'react'; +import styled from '@emotion/native'; +import {useDooboo} from 'dooboo-ui'; +import {ImageBackground, Platform, TouchableOpacity} from 'react-native'; +import {StatType} from '../../../types/github-stats'; +import SvgStatsPerson from '../../svgs/SvgStatsPerson'; +import SvgStatsTree from '../../svgs/SvgStatsTree'; +import SvgStatsFire from '../../svgs/SvgStatsFire'; +import SvgStatsEarth from '../../svgs/SvgStatsEarth'; +import SvgStatsGold from '../../svgs/SvgStatsGold'; +import SvgStatsWater from '../../svgs/SvgStatsWater'; + +type Axis = {x: number; y: number}; + +const Container = styled.View` + padding: 32px; + + justify-content: center; + align-items: center; +`; + +const StatsContainer = styled.View` + flex-direction: column; + align-items: center; + justify-content: center; + padding: 4px; +`; + +const convertPosition = ( + centerPosition: Axis, + percentage: number, + type: StatType, +): string => { + const toString = (axis: Axis): string => `${axis.x},${axis.y}`; + + let threshold: Axis = centerPosition; + let thresholdAxis: Axis = { + x: threshold.x * percentage + centerPosition.x, + y: threshold.y * percentage + centerPosition.y, + }; + let newCenterPosition: Axis = centerPosition; + + switch (type) { + case 'tree': + newCenterPosition = { + x: centerPosition.x + centerPosition.x * 0.18, + y: -centerPosition.y * -0.9, + }; + + threshold = { + x: centerPosition.x, + y: -centerPosition.y * 0.5, + }; + break; + + case 'fire': + newCenterPosition = { + x: centerPosition.x + centerPosition.x * 0.18, + y: centerPosition.y + centerPosition.y * 0.5 * 0.18, + }; + + threshold = { + x: centerPosition.x, + y: centerPosition.y * 0.5, + }; + break; + case 'earth': + newCenterPosition = { + x: centerPosition.x, + y: centerPosition.y + centerPosition.y * 0.18, + }; + + threshold = {x: 0, y: centerPosition.y}; + break; + case 'gold': + newCenterPosition = { + x: centerPosition.x + -(centerPosition.x * 0.18), + y: centerPosition.y + centerPosition.y * 0.5 * 0.18, + }; + + threshold = {x: -centerPosition.x, y: centerPosition.y * 0.5}; + break; + case 'water': + newCenterPosition = { + x: centerPosition.x + -(centerPosition.x * 0.18), + y: centerPosition.y + -(centerPosition.y * 0.5 * 0.18), + }; + + threshold = {x: -centerPosition.x, y: -centerPosition.y * 0.5}; + break; + case 'people': + newCenterPosition = { + x: centerPosition.x, + y: centerPosition.y + -(centerPosition.y * 0.18), + }; + + threshold = {x: 0, y: -centerPosition.y}; + break; + default: + return toString(thresholdAxis); + } + + const newPercentage = (80 * percentage) / 100; + + thresholdAxis = { + x: threshold.x * newPercentage + newCenterPosition.x, + y: threshold.y * newPercentage + newCenterPosition.y, + }; + + return toString(thresholdAxis); +}; + +const StatUnits = ({ + centerPosition, + onPressStat, +}: { + centerPosition: Axis; + onPressStat: (type: StatType) => void; +}): ReactElement => { + return ( + <> + onPressStat('people')} + style={{ + position: 'absolute', + top: 2, + padding: 4, + }} + > + + + onPressStat('tree')} + style={{ + position: 'absolute', + top: centerPosition.y * 0.7 - 4, + right: 2, + padding: 4, + }} + > + + + onPressStat('fire')} + style={{ + position: 'absolute', + bottom: centerPosition.y * 0.7 - 4, + right: 2, + padding: 4, + }} + > + + + onPressStat('earth')} + style={{ + position: 'absolute', + bottom: 2, + padding: 4, + }} + > + + + onPressStat('gold')} + style={{ + position: 'absolute', + bottom: centerPosition.y * 0.7 - 4, + left: 2, + padding: 4, + }} + > + + + onPressStat('water')} + style={{ + position: 'absolute', + top: centerPosition.y * 0.7 - 4, + left: 2, + padding: 4, + }} + > + + + + ); +}; + +export type StatsScore = { + tree: number; + fire: number; + earth: number; + gold: number; + water: number; + people: number; +}; + +export type StatsChartType = { + selectedStat?: StatType | null; + statsScore: StatsScore; + width?: number; + centerPosition?: Axis; + onPressStat: (type: StatType) => void; +}; + +const AnimatedSvg = + Platform.OS === 'web' ? Svg : Animated.createAnimatedComponent(Svg); + +const StatsChart = ({ + selectedStat, + statsScore: {tree, fire, earth, gold, water, people}, + width = 262, + centerPosition = {x: width / 2, y: (width * 1.155) / 2}, + onPressStat, +}: StatsChartType): ReactElement => { + const {theme, themeType} = useDooboo(); + const height = width * 1.155; + + const posFire = convertPosition(centerPosition, fire, 'fire'); + const posEarth = convertPosition(centerPosition, earth, 'earth'); + const posGold = convertPosition(centerPosition, gold, 'gold'); + const posWater = convertPosition(centerPosition, water, 'water'); + const posPerson = convertPosition(centerPosition, people, 'people'); + const posTree = convertPosition(centerPosition, tree, 'tree'); + + return ( + + + + + + + + {/* @ts-ignore - This will be fixed in react-native-svg@13+*/} + + + + + + + + + + + ); +}; + +export default StatsChart; diff --git a/src/components/uis/Scouter/index.tsx b/src/components/uis/Scouter/index.tsx new file mode 100644 index 0000000..5f3a53d --- /dev/null +++ b/src/components/uis/Scouter/index.tsx @@ -0,0 +1,111 @@ +import {memo, useEffect, useState, type ReactElement} from 'react'; +import styled from '@emotion/native'; +import StatsChart, {StatsChartType} from './StatsChart'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import { + DoobooGithubStats, + StatType, + TierType, +} from '../../../types/github-stats'; +import Score, {ScoreType} from './Score'; +import {DEFAULT_GITHUB_STATS} from '../../../utils/constants'; +import CombatDetails from './CombatDetails'; + +const Container = styled.View` + flex-direction: column; + align-items: center; +`; + +const Scouter = ({ + githubLogin, + doobooStats, + chartType, + style, +}: { + githubLogin?: string | null; + doobooStats: DoobooGithubStats; + chartType?: Omit< + StatsChartType, + 'statsScore' | 'onPressStat' | 'selectedStat' + >; + style?: StyleProp; +}): ReactElement => { + const [selectedStat, setSelectedStat] = useState(null); + const [tierName, setTierName] = useState('Silver'); + const pluginStats = !githubLogin + ? DEFAULT_GITHUB_STATS.pluginStats + : doobooStats.pluginStats; + + const sum = + +pluginStats.earth.score + + +pluginStats.fire.score + + +pluginStats.gold.score + + +pluginStats.people.score + + +pluginStats.tree.score + + +pluginStats.water.score; + + const score = Math.round((sum * 100) / 6); + + useEffect(() => { + if (githubLogin && doobooStats.plugin.json) { + const tierJSONArray: TierType[] = JSON.parse( + JSON.stringify(doobooStats.plugin.json), + ); + + const tiers = tierJSONArray.filter((el) => el.score <= score); + if (tiers.length === 0) { + setTierName('Iron'); + + return; + } + + setTierName(tiers[tiers.length - 1].tier as ScoreType['tierName']); + } + }, [doobooStats.plugin.json, githubLogin, score]); + + const onPressStat = (stat: StatType | null): void => { + setSelectedStat(stat); + }; + + return ( + + + {githubLogin ? ( + + + + + + + + + ) : null} + + + + ); +}; + +export default memo(Scouter); diff --git a/src/icons.ts b/src/icons.ts index 5d936f3..b0e643f 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -1,9 +1,28 @@ import ICON from '../assets/icon.png'; -import CROSSPLATFORMS from '../assets/icons/crossplatforms.png'; import GOOGLE from '../assets/icons/google.png'; -import MASK from '../assets/icons/mask.png'; +import CrossPlatforms from '../assets/images/crossplatforms.png'; +import SpiderWebDark from '../assets/images/spider_web_d.png'; +import SpiderWebLight from '../assets/images/spider_web_l.png'; +import TierBronze from '../assets/icons/tier_bronze.png'; +import TierChallenger from '../assets/icons/tier_challenger.png'; +import TierDiamond from '../assets/icons/tier_diamond.png'; +import TierGold from '../assets/icons/tier_gold.png'; +import TierIron from '../assets/icons/tier_iron.png'; +import TierMaster from '../assets/icons/tier_master.png'; +import TierPlatinum from '../assets/icons/tier_platinum.png'; +import TierSilver from '../assets/icons/tier_silver.png'; -export const IC_MASK = MASK; export const IC_ICON = ICON; export const IC_GOOGLE = GOOGLE; -export const IC_CROSSPLATFORMS = CROSSPLATFORMS; +export const IC_TIER_BRONZE = TierBronze; +export const IC_TIER_CHALLENGER = TierChallenger; +export const IC_TIER_DIAMOND = TierDiamond; +export const IC_TIER_GOLD = TierGold; +export const IC_TIER_IRON = TierIron; +export const IC_TIER_MASTER = TierMaster; +export const IC_TIER_PLATINUM = TierPlatinum; +export const IC_TIER_SILVER = TierSilver; + +export const IMG_CROSSPLATFORMS = CrossPlatforms; +export const IMG_SPIDER_WEB_DARK = SpiderWebDark; +export const IMG_SPIDER_WEB_LIGHT = SpiderWebLight; diff --git a/src/types/github-stats.ts b/src/types/github-stats.ts new file mode 100644 index 0000000..dc66da0 --- /dev/null +++ b/src/types/github-stats.ts @@ -0,0 +1,97 @@ +export type StatsDetail = + | { + type: 'repository'; + name: string; + url: string; + } + | { + type: 'language'; + name: string; + count: number; + } + | { + type: 'commit'; + name: string; + message: string; + comment_count: number; + sha: string; + score: number; + author: string; + url: string; + }; + +export type StatsElement = { + key: string; + name: string; + description: string; + totalCount: number; + details?: string; +}; + +export type Stats = { + description: string; + icon: string; + id: string; + name: string; + score: number; + statElements: StatsElement[]; +}; + +export type Plugin = { + name: string; + apiURL: string; + description?: string; + json?: any[]; +}; + +export type TierType = { + tier: string; + score: number; +}; + +export type PluginStats = { + earth: Stats; + fire: Stats; + gold: Stats; + people: Stats; + tree: Stats; + water: Stats; + dooboo: Stats; +}; + +export type DoobooGithubStats = { + json: { + login: string; + avatarUrl?: string; + bio?: string; + score?: number; + languages?: {name: string; color: string; size: number}[]; + }; + plugin: Plugin; + pluginStats: PluginStats; +}; + +export type StatType = 'tree' | 'fire' | 'earth' | 'gold' | 'water' | 'people'; + +export type StatsElementType = { + key: string; + name: string; + description: string; + totalCount: number; + details: string; +}; + +export type StatsElements = { + tree: StatsElementType[]; + fire: StatsElementType[]; + earth: StatsElementType[]; + gold: StatsElementType[]; + water: StatsElementType[]; + people: StatsElementType[]; +}; + +export type ContentDetailDescProps = { + json: DoobooGithubStats['json']; + stats: Stats; + selectedStats: StatType | null; +}; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index ec25c25..0eccca5 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,3 +1,5 @@ +import {DoobooGithubStats, Stats} from '../types/github-stats'; + export const LIST_CNT = 10; export const AsyncStorageKey = { @@ -12,4 +14,32 @@ export const PAGE_SIZE = 10; export const HEADER_HEIGHT = 56; export const MAX_IMAGES_UPLOAD_LENGTH = 5; export const MAX_WIDTH = 1000; -export const EMAIL_ADDRESS = 'crossplatformkorea@gmail.com'; \ No newline at end of file +export const EMAIL_ADDRESS = 'crossplatformkorea@gmail.com'; + +export const initStats: Stats = { + name: '', + description: '', + score: 0.0, + statElements: [], + icon: '', + id: '', +}; + +export const DEFAULT_GITHUB_STATS: DoobooGithubStats = { + json: { + login: '', + }, + pluginStats: { + earth: initStats, + fire: initStats, + gold: initStats, + people: initStats, + tree: initStats, + water: initStats, + dooboo: initStats, + }, + plugin: { + name: 'dooboo-github', + apiURL: '', + }, +};