feat: add api provider
This commit is contained in:
parent
9bfd15819e
commit
3e9cb7f233
24
src/api/ApiContext.ts
Normal file
24
src/api/ApiContext.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { createContext } from 'react'
|
||||||
|
import { createMethod } from './utils'
|
||||||
|
import {
|
||||||
|
User,
|
||||||
|
Auras,
|
||||||
|
Elements,
|
||||||
|
AuthTokens,
|
||||||
|
Assets,
|
||||||
|
AssetCategories,
|
||||||
|
DailyForecasts,
|
||||||
|
} from './resources'
|
||||||
|
|
||||||
|
export interface ApiContextValue {
|
||||||
|
auth: ReturnType<typeof createMethod<AuthTokens.Payload, AuthTokens.Response>>
|
||||||
|
getElements: ReturnType<typeof createMethod<Elements.Payload, Elements.Response>>
|
||||||
|
getUser: ReturnType<typeof createMethod<User.GetPayload, User.Response>>
|
||||||
|
updateUser: ReturnType<typeof createMethod<User.PatchPayload, User.Response>>
|
||||||
|
getAssets: ReturnType<typeof createMethod<Assets.Payload, Assets.Response>>
|
||||||
|
getAssetCategories: ReturnType<typeof createMethod<AssetCategories.Payload, AssetCategories.Response>>
|
||||||
|
getDailyForecasts: ReturnType<typeof createMethod<DailyForecasts.Payload, DailyForecasts.Response>>
|
||||||
|
getAuras: ReturnType<typeof createMethod<Auras.Payload, Auras.Response>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ApiContext = createContext<ApiContextValue>({} as ApiContextValue)
|
||||||
24
src/api/api.ts
Normal file
24
src/api/api.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ApiContextValue } from './ApiContext'
|
||||||
|
import { createMethod } from './utils'
|
||||||
|
import {
|
||||||
|
User,
|
||||||
|
Auras,
|
||||||
|
Elements,
|
||||||
|
AuthTokens,
|
||||||
|
Assets,
|
||||||
|
AssetCategories,
|
||||||
|
DailyForecasts,
|
||||||
|
} from './resources'
|
||||||
|
|
||||||
|
export function createApi(): ApiContextValue {
|
||||||
|
return {
|
||||||
|
auth: createMethod<AuthTokens.Payload, AuthTokens.Response>(AuthTokens.createRequest),
|
||||||
|
getElements: createMethod<Elements.Payload, Elements.Response>(Elements.createRequest),
|
||||||
|
getUser: createMethod<User.GetPayload, User.Response>(User.createGetRequest),
|
||||||
|
updateUser: createMethod<User.PatchPayload, User.Response>(User.createPatchRequest),
|
||||||
|
getAssets: createMethod<Assets.Payload, Assets.Response>(Assets.createRequest),
|
||||||
|
getAssetCategories: createMethod<AssetCategories.Payload, AssetCategories.Response>(AssetCategories.createRequest),
|
||||||
|
getDailyForecasts: createMethod<DailyForecasts.Payload, DailyForecasts.Response>(DailyForecasts.createRequest),
|
||||||
|
getAuras: createMethod<Auras.Payload, Auras.Response>(Auras.createRequest),
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/api/index.ts
Normal file
6
src/api/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from './useApi'
|
||||||
|
export * from './ApiContext'
|
||||||
|
export * from './api'
|
||||||
|
export * from './types'
|
||||||
|
export type { User } from './resources/User'
|
||||||
|
export type { Payload as SignUpPayload } from './resources/AuthTokens'
|
||||||
28
src/api/resources/AssetCategories.ts
Normal file
28
src/api/resources/AssetCategories.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import routes from "../../routes"
|
||||||
|
import { AuthToken } from "../types"
|
||||||
|
import { getAuthHeaders } from "../utils"
|
||||||
|
|
||||||
|
export interface Payload {
|
||||||
|
locale: string
|
||||||
|
token: AuthToken
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Response {
|
||||||
|
asset_categories: AssetCategory[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetCategory {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
slug: string
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createRequest = ({ locale, token }: Payload): Request => {
|
||||||
|
const url = new URL(routes.server.assetCategories())
|
||||||
|
const query = new URLSearchParams({ locale })
|
||||||
|
|
||||||
|
url.search = query.toString()
|
||||||
|
|
||||||
|
return new Request(url, { method: 'GET', headers: getAuthHeaders(token) })
|
||||||
|
}
|
||||||
50
src/api/resources/Assets.ts
Normal file
50
src/api/resources/Assets.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import routes from "../../routes"
|
||||||
|
import { AuthToken } from "../types"
|
||||||
|
import { AssetCategory } from "./AssetCategories"
|
||||||
|
import { getAuthHeaders } from "../utils"
|
||||||
|
|
||||||
|
export interface Payload {
|
||||||
|
token: AuthToken
|
||||||
|
category: string
|
||||||
|
page?: number
|
||||||
|
perPage?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Response {
|
||||||
|
asset_category: AssetCategory
|
||||||
|
assets: Asset[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Asset {
|
||||||
|
id: string
|
||||||
|
url: string
|
||||||
|
asset_data: AssetData
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetData {
|
||||||
|
id: string
|
||||||
|
storage: string
|
||||||
|
metadata: AssetMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetMetadata {
|
||||||
|
size: number
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
filename: string
|
||||||
|
mime_type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createRequest = ({ token, category, page, perPage }: Payload): Request => {
|
||||||
|
const url = new URL(routes.server.assets(category))
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
|
||||||
|
if (page && perPage) {
|
||||||
|
query.append('page', page.toString())
|
||||||
|
query.append('per_page', perPage.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
url.search = query.toString()
|
||||||
|
|
||||||
|
return new Request(url, { method: 'GET', headers: getAuthHeaders(token) })
|
||||||
|
}
|
||||||
54
src/api/resources/Auras.ts
Normal file
54
src/api/resources/Auras.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import routes from "../../routes"
|
||||||
|
import { AuthToken } from "../types"
|
||||||
|
import { getAuthHeaders } from "../utils"
|
||||||
|
|
||||||
|
export interface Payload {
|
||||||
|
token: AuthToken
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Response {
|
||||||
|
user_aura: UserAura
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserAura {
|
||||||
|
updated_at: string
|
||||||
|
viewed_at: string | null
|
||||||
|
aurapic: string | null
|
||||||
|
stats: UserAuraStat[]
|
||||||
|
config: UserAuraConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserAuraStat {
|
||||||
|
stat: string
|
||||||
|
value: number
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserAuraConfig {
|
||||||
|
birthRate: number
|
||||||
|
imageURL: string
|
||||||
|
particleIntensity: number
|
||||||
|
particleIntensityVariation: number
|
||||||
|
particleLifeSpan: number
|
||||||
|
particleSize: number
|
||||||
|
particleSizeVariation: number
|
||||||
|
particleVelocity: number
|
||||||
|
speedFactor: number
|
||||||
|
spreadingAngle: number
|
||||||
|
stretchFactor: number
|
||||||
|
holes: [{
|
||||||
|
from: number
|
||||||
|
to: number
|
||||||
|
}]
|
||||||
|
animations: {
|
||||||
|
[key: string]: {
|
||||||
|
keyTimes: number[]
|
||||||
|
values: number[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createRequest = ({ token }: Payload): Request => {
|
||||||
|
const url = new URL(routes.server.auras())
|
||||||
|
return new Request(url, { method: 'GET', headers: getAuthHeaders(token) })
|
||||||
|
}
|
||||||
37
src/api/resources/AuthTokens.ts
Normal file
37
src/api/resources/AuthTokens.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import routes from "../../routes"
|
||||||
|
import { AuthToken } from "../types"
|
||||||
|
import { User } from "./User"
|
||||||
|
import { getBaseHeaders } from "../utils"
|
||||||
|
|
||||||
|
export interface Payload {
|
||||||
|
email: string
|
||||||
|
timezone: string
|
||||||
|
locale: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Response {
|
||||||
|
auth: {
|
||||||
|
token: AuthToken
|
||||||
|
payload: JwtPayload
|
||||||
|
user: User
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JwtPayload {
|
||||||
|
sub: number
|
||||||
|
email: string
|
||||||
|
loc: string
|
||||||
|
tz: number
|
||||||
|
state: string
|
||||||
|
iat: number
|
||||||
|
exp: number
|
||||||
|
jti: string
|
||||||
|
type: string
|
||||||
|
iss: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createRequest = ({ locale, timezone, email }: Payload): Request => {
|
||||||
|
const url = new URL(routes.server.token())
|
||||||
|
const body = JSON.stringify({ auth: { locale, timezone, email }})
|
||||||
|
return new Request(url, { method: 'POST', headers: getBaseHeaders(), body })
|
||||||
|
}
|
||||||
35
src/api/resources/Elements.ts
Normal file
35
src/api/resources/Elements.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import routes from "../../routes"
|
||||||
|
import { getBaseHeaders } from "../utils"
|
||||||
|
|
||||||
|
export interface Payload {
|
||||||
|
locale: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Response {
|
||||||
|
data: {
|
||||||
|
variant: string
|
||||||
|
groups: ElementGroup[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElementGroup {
|
||||||
|
name: string
|
||||||
|
items: ElementGroupItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElementGroupItem {
|
||||||
|
type: string
|
||||||
|
href: string
|
||||||
|
title: string
|
||||||
|
url_slug: string
|
||||||
|
[key: string]: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createRequest = ({ locale }: Payload): Request => {
|
||||||
|
const url = new URL(routes.server.elements())
|
||||||
|
const query = new URLSearchParams({ locale })
|
||||||
|
|
||||||
|
url.search = query.toString()
|
||||||
|
|
||||||
|
return new Request(url, { method: 'GET', headers: getBaseHeaders() })
|
||||||
|
}
|
||||||
123
src/api/resources/User.ts
Normal file
123
src/api/resources/User.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import routes from "../../routes"
|
||||||
|
import { AuthToken } from "../types"
|
||||||
|
import { getAuthHeaders } from "../utils"
|
||||||
|
|
||||||
|
export interface GetPayload {
|
||||||
|
token: AuthToken
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PatchPayload extends GetPayload {
|
||||||
|
user: Partial<UserPatch>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Response {
|
||||||
|
user: User
|
||||||
|
meta?: {
|
||||||
|
links: {
|
||||||
|
self: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserPatch {
|
||||||
|
locale: string
|
||||||
|
timezone: string
|
||||||
|
profile_attributes: Partial<Pick<UserProfile, 'gender' | 'full_name' | 'relationship_status' | 'birthday'> & {
|
||||||
|
birthplace_id: null
|
||||||
|
birthplace_attributes: {
|
||||||
|
address?: string
|
||||||
|
coords?: string
|
||||||
|
},
|
||||||
|
remote_userpic_url: string
|
||||||
|
}>
|
||||||
|
daily_push_subs_attributes: [{
|
||||||
|
time: string
|
||||||
|
daily_push_id: string
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: string | null | undefined
|
||||||
|
username: string | null
|
||||||
|
email: string
|
||||||
|
locale: string
|
||||||
|
state: string
|
||||||
|
timezone: string
|
||||||
|
new_registration: boolean
|
||||||
|
stat: {
|
||||||
|
last_online_at: string | null
|
||||||
|
prev_online_at: string | null
|
||||||
|
}
|
||||||
|
profile: UserProfile
|
||||||
|
daily_push_subs: Subscription[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserProfile {
|
||||||
|
full_name: string | null
|
||||||
|
gender: string | null
|
||||||
|
birthday: string | null
|
||||||
|
birthplace: UserBirhplace | null
|
||||||
|
age: UserAge | null
|
||||||
|
sign: UserSign | null
|
||||||
|
userpic: UserPic | null
|
||||||
|
userpic_mime_type: string | undefined
|
||||||
|
relationship_status: string
|
||||||
|
human_relationship_status: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserAge {
|
||||||
|
years: number
|
||||||
|
days: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserSign {
|
||||||
|
house: number
|
||||||
|
ruler: string
|
||||||
|
dates: {
|
||||||
|
start: {
|
||||||
|
month: number
|
||||||
|
day: number
|
||||||
|
}
|
||||||
|
end: {
|
||||||
|
month: number
|
||||||
|
day: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sign: string
|
||||||
|
char: string
|
||||||
|
polarity: string
|
||||||
|
modality: string
|
||||||
|
triplicity: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserPic {
|
||||||
|
th: string
|
||||||
|
th2x: string
|
||||||
|
lg: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserBirhplace {
|
||||||
|
id: string
|
||||||
|
address: string
|
||||||
|
coords: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Subscription {
|
||||||
|
id: string
|
||||||
|
daily_push_id: string
|
||||||
|
time: string
|
||||||
|
updated_at: string
|
||||||
|
created_at: string
|
||||||
|
last_sent_at: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createGetRequest = ({ token }: GetPayload): Request => {
|
||||||
|
const url = new URL(routes.server.user())
|
||||||
|
return new Request(url, { method: 'GET', headers: getAuthHeaders(token) })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createPatchRequest = ({ token, user }: PatchPayload): Request => {
|
||||||
|
const url = new URL(routes.server.user())
|
||||||
|
const body = JSON.stringify({ user })
|
||||||
|
return new Request(url, { method: 'PATCH', headers: getAuthHeaders(token), body })
|
||||||
|
}
|
||||||
30
src/api/resources/UserDailyForecasts.ts
Normal file
30
src/api/resources/UserDailyForecasts.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import routes from "../../routes"
|
||||||
|
import { AuthToken } from "../types"
|
||||||
|
import { getAuthHeaders } from "../utils"
|
||||||
|
|
||||||
|
export interface Payload {
|
||||||
|
token: AuthToken
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Response {
|
||||||
|
user_daily_forecast: UserDailyForecast
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserDailyForecast {
|
||||||
|
day: string
|
||||||
|
updated_at: string
|
||||||
|
viewed_at: string | null
|
||||||
|
sign_id: number
|
||||||
|
forecasts: Forecast[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Forecast {
|
||||||
|
category_name: string
|
||||||
|
category: string
|
||||||
|
body: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createRequest = ({ token }: Payload): Request => {
|
||||||
|
const url = new URL(routes.server.dailyForecasts())
|
||||||
|
return new Request(url, { method: 'GET', headers: getAuthHeaders(token) })
|
||||||
|
}
|
||||||
7
src/api/resources/index.ts
Normal file
7
src/api/resources/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export * as Assets from './Assets'
|
||||||
|
export * as AssetCategories from './AssetCategories'
|
||||||
|
export * as User from './User'
|
||||||
|
export * as DailyForecasts from './UserDailyForecasts'
|
||||||
|
export * as Auras from './Auras'
|
||||||
|
export * as Elements from './Elements'
|
||||||
|
export * as AuthTokens from './AuthTokens'
|
||||||
18
src/api/types.ts
Normal file
18
src/api/types.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export interface AuthError {
|
||||||
|
title: string
|
||||||
|
detail: string
|
||||||
|
source: {
|
||||||
|
pointer: string
|
||||||
|
parameter: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiError {
|
||||||
|
base: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorResponse {
|
||||||
|
errors: AuthError[] | ApiError
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthToken = string
|
||||||
4
src/api/useApi.ts
Normal file
4
src/api/useApi.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { useContext } from 'react'
|
||||||
|
import { ApiContext } from './ApiContext'
|
||||||
|
|
||||||
|
export const useApi = () => useContext(ApiContext)
|
||||||
28
src/api/utils.ts
Normal file
28
src/api/utils.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { AuthToken } from "./types"
|
||||||
|
import { ErrorResponse } from "./types"
|
||||||
|
|
||||||
|
export function createMethod<P, R>(createRequest: (payload: P) => Request) {
|
||||||
|
return async (payload: P): Promise<R> => {
|
||||||
|
const request = createRequest(payload)
|
||||||
|
const response = await fetch(request)
|
||||||
|
const data: R & ErrorResponse = await response.json()
|
||||||
|
|
||||||
|
if (response.ok) return data
|
||||||
|
|
||||||
|
const error = Array.isArray(data.errors) ? data.errors[0]?.title : data.errors.base[0]
|
||||||
|
|
||||||
|
throw new Error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBaseHeaders(): Headers {
|
||||||
|
return new Headers({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAuthHeaders(token: AuthToken): Headers {
|
||||||
|
const headers = getBaseHeaders()
|
||||||
|
headers.append('Authorization', `Bearer ${token}`)
|
||||||
|
return headers
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { createContext } from 'react'
|
import { createContext } from 'react'
|
||||||
import { AuthToken, User } from '../types'
|
import { AuthToken, User, SignUpPayload } from '../api'
|
||||||
|
|
||||||
export interface AuthContextValue {
|
export interface AuthContextValue {
|
||||||
user: User | null
|
user: User | null
|
||||||
@ -9,18 +9,4 @@ export interface AuthContextValue {
|
|||||||
addBirthday: (birthday: string, token: AuthToken) => Promise<void>
|
addBirthday: (birthday: string, token: AuthToken) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SignUpPayload {
|
export const AuthContext = createContext<AuthContextValue>({} as AuthContextValue)
|
||||||
email: string
|
|
||||||
timezone: string
|
|
||||||
locale: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialContext: AuthContextValue = {
|
|
||||||
token: '',
|
|
||||||
user: null,
|
|
||||||
logout: () => undefined,
|
|
||||||
signUp: () => Promise.resolve(''),
|
|
||||||
addBirthday: () => Promise.resolve(),
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AuthContext = createContext<AuthContextValue>(initialContext)
|
|
||||||
|
|||||||
@ -1,57 +1,23 @@
|
|||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { AuthContext, SignUpPayload } from './AuthContext'
|
|
||||||
import { RootState, actions } from '../store'
|
import { RootState, actions } from '../store'
|
||||||
import { AuthToken } from '../types'
|
import { useApi, AuthToken, SignUpPayload } from '../api'
|
||||||
import routes from '../routes'
|
import { AuthContext } from './AuthContext'
|
||||||
|
|
||||||
export function AuthProvider({ children }: React.PropsWithChildren<unknown>): JSX.Element {
|
export function AuthProvider({ children }: React.PropsWithChildren<unknown>): JSX.Element {
|
||||||
|
const api = useApi()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const token = useSelector((state: RootState) => state.token)
|
const token = useSelector((state: RootState) => state.token)
|
||||||
const user = useSelector((state: RootState) => state.user)
|
const user = useSelector((state: RootState) => state.user)
|
||||||
const signUp = async ({ email, timezone, locale }: SignUpPayload): Promise<AuthToken> => {
|
const signUp = async (payload: SignUpPayload): Promise<AuthToken> => {
|
||||||
const response = await fetch(routes.server.token(), {
|
const { auth: { token, user } } = await api.auth(payload)
|
||||||
method: 'POST',
|
dispatch(actions.token.update(token))
|
||||||
headers: { 'Content-Type': 'application/json' },
|
dispatch(actions.user.update(user))
|
||||||
body: JSON.stringify({ auth: { email, timezone, locale } })
|
return token
|
||||||
})
|
|
||||||
if (response.ok) {
|
|
||||||
const { auth: { token, user } } = await response.json()
|
|
||||||
dispatch(actions.token.update(token))
|
|
||||||
dispatch(actions.user.update(user))
|
|
||||||
return token
|
|
||||||
} else {
|
|
||||||
const { errors } = await response.json()
|
|
||||||
if (Array.isArray(errors)) {
|
|
||||||
const [error] = errors
|
|
||||||
throw new Error(error.title)
|
|
||||||
} else {
|
|
||||||
const { base: [error] } = errors
|
|
||||||
throw new Error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const addBirthday = async (birthday: string, token: AuthToken): Promise<void> => {
|
const addBirthday = async (birthday: string, token: AuthToken): Promise<void> => {
|
||||||
const response = await fetch(routes.server.user(), {
|
const payload = { user: { profile_attributes: { birthday } }, token }
|
||||||
method: 'PATCH',
|
const { user } = await api.updateUser(payload)
|
||||||
headers: {
|
dispatch(actions.user.update(user))
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ user: { profile_attributes: { birthday } } })
|
|
||||||
})
|
|
||||||
if (response.ok) {
|
|
||||||
const { user } = await response.json()
|
|
||||||
dispatch(actions.user.update(user))
|
|
||||||
} else {
|
|
||||||
const { errors } = await response.json()
|
|
||||||
if (Array.isArray(errors)) {
|
|
||||||
const [error] = errors
|
|
||||||
throw new Error(error.title)
|
|
||||||
} else {
|
|
||||||
const { base: [error] } = errors
|
|
||||||
throw new Error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const logout = () => dispatch(actions.reset())
|
const logout = () => dispatch(actions.reset())
|
||||||
const auth = { signUp, logout, addBirthday, token, user: user.id ? user : null }
|
const auth = { signUp, logout, addBirthday, token, user: user.id ? user : null }
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
import './styles.css'
|
import './styles.css'
|
||||||
|
|
||||||
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
|
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||||
|
color?: 'black' | 'blue'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
function MainButton({ className, children, ...props}: ButtonProps): JSX.Element {
|
function MainButton({ className, children, color, ...props}: ButtonProps): JSX.Element {
|
||||||
const combinedClassNames = ['main-btn', className].filter(Boolean).join(' ')
|
const colorClass = color ? `main-btn--${color}` : 'main-btn--black'
|
||||||
|
const combinedClassNames = ['main-btn', colorClass, className].filter(Boolean).join(' ')
|
||||||
return <button className={combinedClassNames} {...props}>{children}</button>
|
return <button className={combinedClassNames} {...props}>{children}</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
.main-btn {
|
.main-btn {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #000;
|
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
@ -22,3 +20,13 @@
|
|||||||
.main-btn:disabled {
|
.main-btn:disabled {
|
||||||
opacity: 50%;
|
opacity: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-btn--black {
|
||||||
|
background: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-btn--blue {
|
||||||
|
background: #306ed7;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
22
src/init.tsx
22
src/init.tsx
@ -1,30 +1,30 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
|
||||||
import i18next from 'i18next'
|
import i18next from 'i18next'
|
||||||
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import { I18nextProvider, initReactI18next } from 'react-i18next'
|
import { I18nextProvider, initReactI18next } from 'react-i18next'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import { store } from './store'
|
import { store } from './store'
|
||||||
import resources from './locales'
|
|
||||||
import routes from './routes'
|
|
||||||
import { AuthProvider } from './auth'
|
import { AuthProvider } from './auth'
|
||||||
|
import { ApiContext, createApi } from './api'
|
||||||
|
import resources, { getClientLocale } from './locales'
|
||||||
import App from './components/App'
|
import App from './components/App'
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
const response = await fetch(routes.server.translations())
|
const api = createApi()
|
||||||
const data = await response.json()
|
const lng = getClientLocale()
|
||||||
const defaultLanguage = data.meta.locale
|
|
||||||
// TODO: add translations from data.translations
|
|
||||||
const i18nextInstance = i18next.createInstance()
|
const i18nextInstance = i18next.createInstance()
|
||||||
const options = { lng: defaultLanguage, resources }
|
const options = { lng, resources }
|
||||||
await i18nextInstance.use(initReactI18next).init(options)
|
await i18nextInstance.use(initReactI18next).init(options)
|
||||||
return (
|
return (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<I18nextProvider i18n={i18nextInstance}>
|
<I18nextProvider i18n={i18nextInstance}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<AuthProvider>
|
<ApiContext.Provider value={api}>
|
||||||
<App />
|
<AuthProvider>
|
||||||
</AuthProvider>
|
<App />
|
||||||
|
</AuthProvider>
|
||||||
|
</ApiContext.Provider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</Provider>
|
</Provider>
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
|
|||||||
@ -10,13 +10,17 @@ const routes = {
|
|||||||
emailEnter: () => [host, 'email'].join('/'),
|
emailEnter: () => [host, 'email'].join('/'),
|
||||||
subscription: () => [host, 'subscription'].join('/'),
|
subscription: () => [host, 'subscription'].join('/'),
|
||||||
createProfile: () => [host, 'profile', 'create'].join('/'),
|
createProfile: () => [host, 'profile', 'create'].join('/'),
|
||||||
|
paymentMethod: () => [host, 'payment', 'method'].join('/'),
|
||||||
|
wallpaper: () => [host, 'wallpaper'].join('/'),
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
locales: () => [apiHost, prefix, 'locales.json'].join('/'),
|
|
||||||
translations: () => [apiHost, prefix, 't.json'].join('/'),
|
|
||||||
elements: () => [apiHost, prefix, 'elements.json'].join('/'),
|
elements: () => [apiHost, prefix, 'elements.json'].join('/'),
|
||||||
user: () => [apiHost, prefix, 'user.json'].join('/'),
|
user: () => [apiHost, prefix, 'user.json'].join('/'),
|
||||||
token: () => [apiHost, prefix, 'auth', 'token.json'].join('/'),
|
token: () => [apiHost, prefix, 'auth', 'token.json'].join('/'),
|
||||||
|
assets: (category: string) => [apiHost, prefix, 'assets', 'categories', `${category}.json`].join('/'),
|
||||||
|
assetCategories: () => [apiHost, prefix, 'assets', 'categories.json'].join('/'),
|
||||||
|
dailyForecasts: () => [apiHost, prefix, 'user', 'daily_forecasts.json'].join('/'),
|
||||||
|
auras: () => [apiHost, prefix, 'user', 'aura.json'].join('/'),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit'
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit'
|
import type { PayloadAction } from '@reduxjs/toolkit'
|
||||||
import type { AuthToken } from '../types'
|
import type { AuthToken } from '../api'
|
||||||
|
|
||||||
const initialState: AuthToken = ''
|
const initialState: AuthToken = ''
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit'
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit'
|
import type { PayloadAction } from '@reduxjs/toolkit'
|
||||||
import type { User } from '../types'
|
import type { User } from '../api'
|
||||||
import { getClientLocale, getClientTimezone } from '../locales'
|
import { getClientLocale, getClientTimezone } from '../locales'
|
||||||
|
|
||||||
const initialState: User = {
|
const initialState: User = {
|
||||||
|
|||||||
77
src/types.ts
77
src/types.ts
@ -12,80 +12,3 @@ export interface SignupForm {
|
|||||||
birthdate: string
|
birthdate: string
|
||||||
birthtime: string
|
birthtime: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AuthToken = string
|
|
||||||
|
|
||||||
export interface User {
|
|
||||||
id: string | null | undefined
|
|
||||||
username: string | null
|
|
||||||
email: string
|
|
||||||
locale: string
|
|
||||||
state: string
|
|
||||||
timezone: string
|
|
||||||
new_registration: boolean
|
|
||||||
stat: {
|
|
||||||
last_online_at: string | null
|
|
||||||
prev_online_at: string | null
|
|
||||||
}
|
|
||||||
profile: UserProfile
|
|
||||||
daily_push_subs: Subscription[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserProfile {
|
|
||||||
full_name: string | null
|
|
||||||
gender: string | null
|
|
||||||
birthday: string | null
|
|
||||||
birthplace: UserBirhplace | null
|
|
||||||
age: UserAge | null
|
|
||||||
sign: UserSign | null
|
|
||||||
userpic: UserPic | null
|
|
||||||
userpic_mime_type: string | undefined
|
|
||||||
relationship_status: string
|
|
||||||
human_relationship_status: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserAge {
|
|
||||||
years: number
|
|
||||||
days: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserSign {
|
|
||||||
house: number
|
|
||||||
ruler: string
|
|
||||||
dates: {
|
|
||||||
start: {
|
|
||||||
month: number
|
|
||||||
day: number
|
|
||||||
}
|
|
||||||
end: {
|
|
||||||
month: number
|
|
||||||
day: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sign: string
|
|
||||||
char: string
|
|
||||||
polarity: string
|
|
||||||
modality: string
|
|
||||||
triplicity: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserPic {
|
|
||||||
th: string
|
|
||||||
th2x: string
|
|
||||||
lg: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserBirhplace {
|
|
||||||
id: string
|
|
||||||
address: string
|
|
||||||
coords: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Subscription {
|
|
||||||
id: string
|
|
||||||
daily_push_id: string
|
|
||||||
time: string
|
|
||||||
updated_at: string
|
|
||||||
created_at: string
|
|
||||||
last_sent_at: string | null
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user