diff --git a/.env.development b/.env.development deleted file mode 100644 index d0f2796769..0000000000 --- a/.env.development +++ /dev/null @@ -1 +0,0 @@ -REACT_APP_BUILD = 'staging' diff --git a/.env.production b/.env.production deleted file mode 100644 index d0f2796769..0000000000 --- a/.env.production +++ /dev/null @@ -1 +0,0 @@ -REACT_APP_BUILD = 'staging' diff --git a/src/env.native.ts b/src/env.native.ts deleted file mode 100644 index a2ec3a4dce..0000000000 --- a/src/env.native.ts +++ /dev/null @@ -1,13 +0,0 @@ -// @ts-ignore types not available -prf -import {REACT_APP_BUILD} from '@env' - -if (typeof REACT_APP_BUILD !== 'string') { - throw new Error('ENV: No env provided') -} -if (!['dev', 'staging', 'prod'].includes(REACT_APP_BUILD)) { - throw new Error( - `ENV: Env must be "dev", "staging", or "prod," got "${REACT_APP_BUILD}"`, - ) -} - -export const BUILD = REACT_APP_BUILD diff --git a/src/env.ts b/src/env.ts deleted file mode 100644 index a379e435f2..0000000000 --- a/src/env.ts +++ /dev/null @@ -1,10 +0,0 @@ -if (typeof process.env.REACT_APP_BUILD !== 'string') { - throw new Error('ENV: No env provided') -} -if (!['dev', 'staging', 'prod'].includes(process.env.REACT_APP_BUILD)) { - throw new Error( - `ENV: Env must be "dev", "staging", or "prod," got "${process.env.REACT_APP_BUILD}"`, - ) -} - -export const BUILD = process.env.REACT_APP_BUILD diff --git a/src/state/index.ts b/src/state/index.ts index a886e7611f..b16b516485 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -3,14 +3,12 @@ import {sessionClient as AtpApi} from '../third-party/api' import {RootStoreModel} from './models/root-store' import * as libapi from './lib/api' import * as storage from './lib/storage' -import {BUILD} from '../env' -export const DEFAULT_SERVICE = - BUILD === 'prod' - ? 'http://localhost:2583' // TODO - : BUILD === 'staging' - ? 'https://pds.staging.bsky.dev' // TODO - : 'http://localhost:2583' +export const IS_PROD_BUILD = true +export const LOCAL_DEV_SERVICE = 'http://localhost:2583' +export const STAGING_SERVICE = 'https://pds.staging.bsky.dev' +export const PROD_SERVICE = 'https://plc.bsky.social' +export const DEFAULT_SERVICE = IS_PROD_BUILD ? PROD_SERVICE : LOCAL_DEV_SERVICE const ROOT_STATE_STORAGE_KEY = 'root' const STATE_FETCH_INTERVAL = 15e3 diff --git a/src/state/models/shell-ui.ts b/src/state/models/shell-ui.ts index cc884f1c36..73b1bd56e7 100644 --- a/src/state/models/shell-ui.ts +++ b/src/state/models/shell-ui.ts @@ -66,6 +66,17 @@ export class InviteToSceneModel { } } +export class ServerInputModel { + name = 'server-input' + + constructor( + public initialService: string, + public onSelect: (url: string) => void, + ) { + makeAutoObservable(this) + } +} + export interface ComposerOpts { replyTo?: Post.PostRef onPost?: () => void @@ -79,6 +90,7 @@ export class ShellUiModel { | SharePostModel | EditProfileModel | CreateSceneModel + | ServerInputModel | undefined isComposerActive = false composerOpts: ComposerOpts | undefined @@ -93,7 +105,8 @@ export class ShellUiModel { | ConfirmModel | SharePostModel | EditProfileModel - | CreateSceneModel, + | CreateSceneModel + | ServerInputModel, ) { this.isModalActive = true this.activeModal = modal diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index f2c61a6ae7..210cdc41f8 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -13,6 +13,7 @@ import * as SharePostModal from './SharePost.native' import * as EditProfileModal from './EditProfile' import * as CreateSceneModal from './CreateScene' import * as InviteToSceneModal from './InviteToScene' +import * as ServerInputModal from './ServerInput' const CLOSED_SNAPPOINTS = ['10%'] @@ -77,6 +78,13 @@ export const Modal = observer(function Modal() { {...(store.shell.activeModal as models.InviteToSceneModel)} /> ) + } else if (store.shell.activeModal?.name === 'server-input') { + snapPoints = ServerInputModal.snapPoints + element = ( + + ) } else { element = } diff --git a/src/view/com/modals/ServerInput.tsx b/src/view/com/modals/ServerInput.tsx new file mode 100644 index 0000000000..1f3cc90f93 --- /dev/null +++ b/src/view/com/modals/ServerInput.tsx @@ -0,0 +1,140 @@ +import React, {useState} from 'react' +import Toast from '../util/Toast' +import {StyleSheet, Text, TextInput, TouchableOpacity, View} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import LinearGradient from 'react-native-linear-gradient' +import {ErrorMessage} from '../util/ErrorMessage' +import {useStores} from '../../../state' +import {ProfileViewModel} from '../../../state/models/profile-view' +import {s, colors, gradients} from '../../lib/styles' +import {enforceLen, MAX_DISPLAY_NAME, MAX_DESCRIPTION} from '../../lib/strings' +import { + IS_PROD_BUILD, + LOCAL_DEV_SERVICE, + STAGING_SERVICE, + PROD_SERVICE, +} from '../../../state/index' + +export const snapPoints = ['80%'] + +export function Component({ + initialService, + onSelect, +}: { + initialService: string + onSelect: (url: string) => void +}) { + const store = useStores() + const [customUrl, setCustomUrl] = useState('') + + const doSelect = (url: string) => { + if (!url.startsWith('http://') && !url.startsWith('https://')) { + url = `https://${url}` + } + store.shell.closeModal() + onSelect(url) + } + + return ( + + Choose Service + + + {!IS_PROD_BUILD ? ( + <> + doSelect(LOCAL_DEV_SERVICE)}> + Local dev server + + + doSelect(STAGING_SERVICE)}> + Staging + + + + ) : undefined} + doSelect(PROD_SERVICE)}> + Bluesky.Social + + + + + Other service + + + doSelect(customUrl)}> + + + + + + + ) +} + +const styles = StyleSheet.create({ + inner: { + padding: 14, + }, + group: { + marginBottom: 20, + }, + label: { + fontWeight: 'bold', + paddingHorizontal: 4, + paddingBottom: 4, + }, + textInput: { + flex: 1, + borderWidth: 1, + borderColor: colors.gray3, + borderTopLeftRadius: 6, + borderBottomLeftRadius: 6, + paddingHorizontal: 14, + paddingVertical: 12, + fontSize: 16, + }, + textInputBtn: { + borderWidth: 1, + borderLeftWidth: 0, + borderColor: colors.gray3, + borderTopRightRadius: 6, + borderBottomRightRadius: 6, + paddingHorizontal: 14, + paddingVertical: 10, + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.blue3, + borderRadius: 6, + paddingHorizontal: 14, + paddingVertical: 10, + marginBottom: 6, + }, + btnText: { + flex: 1, + fontSize: 18, + fontWeight: '500', + color: colors.white, + }, +}) diff --git a/src/view/index.ts b/src/view/index.ts index 341051d4e0..78361e75b4 100644 --- a/src/view/index.ts +++ b/src/view/index.ts @@ -5,6 +5,7 @@ import {faAngleDown} from '@fortawesome/free-solid-svg-icons/faAngleDown' import {faAngleLeft} from '@fortawesome/free-solid-svg-icons/faAngleLeft' import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight' import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft' +import {faArrowRight} from '@fortawesome/free-solid-svg-icons/faArrowRight' import {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons' import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket' import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare' @@ -35,6 +36,7 @@ import {faLock} from '@fortawesome/free-solid-svg-icons/faLock' import {faMagnifyingGlass} from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass' import {faMessage} from '@fortawesome/free-regular-svg-icons/faMessage' import {faNoteSticky} from '@fortawesome/free-solid-svg-icons/faNoteSticky' +import {faPen} from '@fortawesome/free-solid-svg-icons/faPen' import {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib' import {faPenToSquare} from '@fortawesome/free-solid-svg-icons/faPenToSquare' import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus' @@ -59,6 +61,7 @@ export function setup() { faAngleLeft, faAngleRight, faArrowLeft, + faArrowRight, faArrowRightFromBracket, faArrowUpFromBracket, faArrowUpRightFromSquare, @@ -89,6 +92,7 @@ export function setup() { faMagnifyingGlass, faMessage, faNoteSticky, + faPen, faPenNib, faPenToSquare, faPlus, diff --git a/src/view/lib/strings.ts b/src/view/lib/strings.ts index 214bb51d63..05b23331f3 100644 --- a/src/view/lib/strings.ts +++ b/src/view/lib/strings.ts @@ -1,5 +1,6 @@ import {AtUri} from '../../third-party/uri' import {Entity} from '../../third-party/api/src/client/types/app/bsky/feed/post' +import {PROD_SERVICE} from '../../state' export const MAX_DISPLAY_NAME = 64 export const MAX_DESCRIPTION = 256 @@ -106,3 +107,15 @@ export function cleanError(str: string): string { } return str } + +export function toNiceDomain(url: string): string { + try { + const urlp = new URL(url) + if (`https://${urlp.host}` === PROD_SERVICE) { + return 'Bluesky.Social' + } + return urlp.host + } catch (e) { + return url + } +} diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 036f7d1487..04ce2d0cb9 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -8,7 +8,6 @@ import {useStores} from '../../state' import {FeedModel} from '../../state/models/feed-view' import {ScreenParams} from '../routes' import {s} from '../lib/styles' -import {BUILD} from '../../env' export const Home = observer(function Home({ visible, @@ -57,10 +56,7 @@ export const Home = observer(function Home({ return ( - + Bluesky - - [ private beta {BUILD !== 'prod' ? `- ${BUILD} ` : ''}] - + [ private beta ] @@ -112,6 +111,7 @@ const SigninOrCreateAccount = ({ const Signin = ({onPressBack}: {onPressBack: () => void}) => { const store = useStores() const [isProcessing, setIsProcessing] = useState(false) + const [serviceUrl, setServiceUrl] = useState(DEFAULT_SERVICE) const [serviceDescription, setServiceDescription] = useState< ServiceDescription | undefined >(undefined) @@ -121,10 +121,9 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => { useEffect(() => { let aborted = false - if (serviceDescription || error) { - return - } - store.session.describeService(DEFAULT_SERVICE).then( + setError('') + console.log('Fetching service description', serviceUrl) + store.session.describeService(serviceUrl).then( desc => { if (aborted) return setServiceDescription(desc) @@ -140,7 +139,11 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => { return () => { aborted = true } - }, []) + }, [serviceUrl]) + + const onPressSelectService = () => { + store.shell.openModal(new ServerInputModel(serviceUrl, setServiceUrl)) + } const onPressNext = async () => { setError('') @@ -168,7 +171,7 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => { } await store.session.login({ - service: DEFAULT_SERVICE, + service: serviceUrl, handle: fullHandle, password, }) @@ -194,9 +197,14 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => { - - Sign in - + + + Sign in to {toNiceDomain(serviceUrl)} + + + {error ? ( @@ -256,6 +264,7 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => { const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { const store = useStores() const [isProcessing, setIsProcessing] = useState(false) + const [serviceUrl, setServiceUrl] = useState(DEFAULT_SERVICE) const [error, setError] = useState('') const [serviceDescription, setServiceDescription] = useState< ServiceDescription | undefined @@ -268,10 +277,9 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { useEffect(() => { let aborted = false - if (serviceDescription || error) { - return - } - store.session.describeService(DEFAULT_SERVICE).then( + setError('') + console.log('Fetching service description', serviceUrl) + store.session.describeService(serviceUrl).then( desc => { if (aborted) return setServiceDescription(desc) @@ -288,7 +296,11 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { return () => { aborted = true } - }, []) + }, [serviceUrl]) + + const onPressSelectService = () => { + store.shell.openModal(new ServerInputModel(serviceUrl, setServiceUrl)) + } const onPressNext = async () => { if (!email) { @@ -307,7 +319,7 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { setIsProcessing(true) try { await store.session.createAccount({ - service: DEFAULT_SERVICE, + service: serviceUrl, email, handle: createFullHandle(handle, userDomain), password, @@ -346,136 +358,164 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { ) return ( - - - - - {serviceDescription ? ( - <> - {error ? ( - - - + + + + + + {serviceDescription ? ( + <> + {error ? ( + + + + + + {error} + - - {error} + ) : undefined} + + + + Create a new account + - - ) : undefined} - - - Create a new account - - {serviceDescription?.inviteCodeRequired ? ( + + + + + {toNiceDomain(serviceUrl)} + + + + + {serviceDescription?.inviteCodeRequired ? ( + + + + + ) : undefined} - ) : undefined} - - - - - - - - - - - - Choose your username - - - - setHandle(makeValidHandle(v))} - editable={!isProcessing} - /> - - {serviceDescription.availableUserDomains.length > 1 && ( - - ({ - label: `.${d}`, - value: d, - }))} - onChange={itemValue => setUserDomain(itemValue)} - enabled={!isProcessing} + + - )} - - - Your full username will be{' '} - - @{createFullHandle(handle, userDomain)} - - - - - - Back - - - - {isProcessing ? ( - - ) : ( - Next + + + + Choose your username + + + + + setHandle(makeValidHandle(v))} + editable={!isProcessing} + /> + + {serviceDescription.availableUserDomains.length > 1 && ( + + + ({ + label: `.${d}`, + value: d, + }))} + onChange={itemValue => setUserDomain(itemValue)} + enabled={!isProcessing} + /> + )} - - - - ) : ( - - )} - + + + Your full username will be{' '} + + @{createFullHandle(handle, userDomain)} + + + + + + + Back + + + + {isProcessing ? ( + + ) : ( + Next + )} + + + + ) : ( + + )} + + ) } @@ -577,9 +617,15 @@ const styles = StyleSheet.create({ backgroundColor: colors.blue3, }, groupTitle: { + flexDirection: 'row', + alignItems: 'center', paddingVertical: 8, paddingHorizontal: 12, }, + groupTitleIcon: { + color: colors.white, + marginHorizontal: 6, + }, groupContent: { borderTopWidth: 1, borderTopColor: colors.blue1, @@ -600,6 +646,22 @@ const styles = StyleSheet.create({ fontSize: 18, borderRadius: 10, }, + textBtn: { + flexDirection: 'row', + flex: 1, + alignItems: 'center', + }, + textBtnLabel: { + flex: 1, + color: colors.white, + paddingVertical: 10, + paddingHorizontal: 12, + fontSize: 18, + }, + textBtnIcon: { + color: colors.white, + marginHorizontal: 12, + }, picker: { flex: 1, width: '100%', diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx index 712d6dc23d..96390e9b83 100644 --- a/src/view/shell/mobile/index.tsx +++ b/src/view/shell/mobile/index.tsx @@ -170,6 +170,7 @@ export const MobileShell: React.FC = observer(() => { + ) } @@ -294,6 +295,7 @@ function constructScreenRenderDesc(nav: NavigationModel): { const styles = StyleSheet.create({ outerContainer: { height: '100%', + flex: 1, }, innerContainer: { flex: 1, diff --git a/todos.txt b/todos.txt index e9879bf4c0..da966aa245 100644 --- a/todos.txt +++ b/todos.txt @@ -6,6 +6,7 @@ Paul's todo list - Cursor behaviors on all views - Update swipe behaviors: edge always goes back, leftmost always goes back, main connects to selector if present - Onboarding flow + > Invite codes - Confirm email - Setup profile? - Onboarding @@ -23,7 +24,7 @@ Paul's todo list - View on post - Linking - Web linking - - App linking + > App linking - Pagination - Liked by - Reposted by @@ -33,7 +34,4 @@ Paul's todo list - Bugs - Auth token refresh seems broken - Check that sub components arent reloading too much - - Titles are getting screwed up (possibly swipe related) - > Dont suggest self for follows - > Double post on post? - > Handle no displayname everywhere \ No newline at end of file + - Titles are getting screwed up (possibly swipe related) \ No newline at end of file