Map of Pi is a mobile application developed to help Pi community members easily locate local businesses that accept Pi as payment. This project was initiated as part of the Pi Commerce Hackathon with the goal of facilitating Pi transactions and connecting businesses with the Pi community.
14 |
15 |
16 | ## Table of Contents
17 |
18 | - [Brand Design](#brand-design)
19 | - [Tech Stack](#tech-stack)
20 | - [Frontend Local Execution](#frontend-local-execution)
21 | - [Team](#team)
22 | - [Contributions](#contributions)
23 |
24 | ## Brand Design
25 |
26 | | App Logo | App Icon |
27 | | ------------- |:-------------:|
28 | | |
29 |
30 | ## Tech Stack 📊
31 |
32 | - **Frontend**: NextJS/ React, TypeScript, HTML, SCSS, CSS
33 | - **Backend**: Express/ NodeJS, REST API
34 | - **Database**: MongoDB
35 | - **DevOps**: GitHub Actions, Netlify, Vercel
36 |
37 | ## Frontend Local Execution
38 |
39 | The Map of Pi Front End is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) which is a React framework to build web applications.
40 |
41 | ### Build the Project
42 |
43 | - Run `npm run build` to build the project; builds the app for production to the `.next` folder.
44 | - The build artifacts are bundled for production mode and optimized for the best performance.
45 |
46 | ### Execute the Development Server
47 |
48 | - Create .env.local file from the .env.development template and replace placeholders with actual values.
49 | - Execute `npm run dev` to spin up a dev server.
50 | - Navigate to http://localhost:4200/ in your browser.
51 | - Execute **[Backend Local Execution](https://github.com/map-of-pi/map-of-pi-backend-react/blob/dev/README.md#backend-local-execution)** for integration testing.
52 | - The application will automatically reload if you change any of the source files.
53 | - For local debugging in VS Code, attach the runtime server accordingly.
54 | - Lint errors will be displayed in the console.
55 |
56 | ### Execute Unit Tests
57 |
58 | - Run `npm run test` to launch the [Jest](https://jestjs.io/) Testing Framework in the interactive watch mode.
59 |
60 | ### Linting the Project
61 |
62 | - Run `npm run lint` for static code analysis.
63 |
64 | ## Team 🧑👩🦱🧔👨🏾🦱👨🏾
65 |
66 | ### Project Manager
67 | - Philip Jennings
68 |
69 | ### Marketing
70 | - Bonnie Ford
71 | - Joseph Ciccone
72 |
73 | ### Solution Design / UX
74 | - Femma Ashraf
75 | - Oluwabukola Adesina
76 | - Folorunsho Omotunde
77 | - Henry Fasakin
78 |
79 | ### Technical Lead/ DevOps
80 | - Danny Lee
81 |
82 | ### Technical Advisor
83 | - Zoltan Magyar
84 |
85 | ### Application Developers
86 | - Darin Hajou
87 | - Rokundo Soleil
88 | - Ayomikun Omotosho
89 | - Yusuf Adisa
90 | - Francis Mwaura
91 | - Samuel Oluyomi
92 |
93 | ## Contributions
94 |
95 |
96 |
We welcome contributions from the community to improve the Map of Pi project.
97 |
98 |
--------------------------------------------------------------------------------
/context/AppContextProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import 'react-toastify/dist/ReactToastify.css';
4 |
5 | import { useTranslations } from 'next-intl';
6 | import {
7 | createContext,
8 | useState,
9 | SetStateAction,
10 | ReactNode,
11 | useEffect
12 | } from 'react';
13 |
14 | import { Pi } from '@pinetwork-js/sdk';
15 | import axiosClient, {setAuthToken} from '@/config/client';
16 | import { onIncompletePaymentFound } from '@/utils/auth';
17 | import { AuthResult } from '@/constants/pi';
18 | import { IUser } from '@/constants/types';
19 |
20 | import logger from '../logger.config.mjs';
21 |
22 | interface IAppContextProps {
23 | currentUser: IUser | null;
24 | setCurrentUser: React.Dispatch>;
25 | registerUser: () => void;
26 | autoLoginUser: ()=> void;
27 | isSigningInUser: boolean;
28 | reload: boolean;
29 | alertMessage: string | null;
30 | setAlertMessage: React.Dispatch>;
31 | showAlert: (message: string) => void;
32 | setReload: React.Dispatch>;
33 | isSaveLoading: boolean;
34 | setIsSaveLoading: React.Dispatch>;
35 | }
36 |
37 | const initialState: IAppContextProps = {
38 | currentUser: null,
39 | setCurrentUser: () => {},
40 | registerUser: () => { },
41 | autoLoginUser: ()=> {},
42 | isSigningInUser: false,
43 | reload: false,
44 | alertMessage: null,
45 | setAlertMessage: () => {},
46 | showAlert: () => {},
47 | setReload: () => {},
48 | isSaveLoading: false,
49 | setIsSaveLoading: () => {}
50 | };
51 |
52 | export const AppContext = createContext(initialState);
53 |
54 | interface AppContextProviderProps {
55 | children: ReactNode;
56 | }
57 |
58 | const AppContextProvider = ({ children }: AppContextProviderProps) => {
59 | const t = useTranslations();
60 | const [currentUser, setCurrentUser] = useState(null);
61 | const [isSigningInUser, setIsSigningInUser] = useState(false);
62 | const [reload, setReload] = useState(false);
63 | const [isSaveLoading, setIsSaveLoading] = useState(false);
64 |
65 | const [alertMessage, setAlertMessage] = useState(null);
66 |
67 | const showAlert = (message: string) => {
68 | setAlertMessage(message);
69 | setTimeout(() => {
70 | setAlertMessage(null); // Clear alert after 5 seconds
71 | }, 5000);
72 | };
73 |
74 | const registerUser = async () => {
75 | logger.info('Initializing Pi SDK for user registration.');
76 | await Pi.init({ version: '2.0', sandbox: process.env.NODE_ENV === 'development' });
77 | let isInitiated = Pi.initialized;
78 |
79 | if (isInitiated) {
80 | try {
81 | setIsSigningInUser(true);
82 | const pioneerAuth: AuthResult = await window.Pi.authenticate(['username', 'payments'], onIncompletePaymentFound);
83 | const res = await axiosClient.post(
84 | "/users/authenticate",
85 | {}, // empty body
86 | {
87 | headers: {
88 | Authorization: `Bearer ${pioneerAuth.accessToken}`,
89 | },
90 | }
91 | );
92 |
93 | if (res.status === 200) {
94 | setAuthToken(res.data?.token);
95 | setCurrentUser(res.data.user);
96 | logger.info('User authenticated successfully.');
97 | setTimeout(() => {
98 | setIsSigningInUser(false); // hide the splash screen after the delay
99 | }, 2500);
100 | } else if (res.status === 500) {
101 | setCurrentUser(null);
102 | logger.error('User authentication failed.');
103 | setIsSigningInUser(false);
104 | }
105 | } catch (error) {
106 | logger.error('Error during user registration:', error);
107 | setIsSigningInUser(false);
108 | }
109 | } else {
110 | logger.error('PI SDK failed to initialize.');
111 | }
112 | };
113 |
114 | const autoLoginUser = async () => {
115 | logger.info('Attempting to auto-login user.');
116 | try {
117 | setIsSigningInUser(true);
118 | const res = await axiosClient.get('/users/me');
119 |
120 | if (res.status === 200) {
121 | logger.info('Auto-login successful.');
122 | setCurrentUser(res.data);
123 | setTimeout(() => {
124 | setIsSigningInUser(false); // hide the splash screen after the delay
125 | }, 2500);
126 | } else {
127 | setCurrentUser(null);
128 | logger.warn('Auto-login failed.');
129 | setIsSigningInUser(false);
130 | }
131 | } catch (error) {
132 | logger.error('Auto login unresolved; attempting Pi SDK authentication:', error);
133 | await registerUser();
134 | }
135 | }
136 |
137 | useEffect(() => {
138 | logger.info('AppContextProvider mounted.');
139 | if (!currentUser) {
140 | registerUser();
141 | } else {
142 | autoLoginUser();
143 | }
144 | }, []);
145 |
146 | return (
147 |
148 | {children}
149 |
150 | );
151 | };
152 |
153 | export default AppContextProvider;
154 |
--------------------------------------------------------------------------------
/i18n/i18n.ts:
--------------------------------------------------------------------------------
1 | // Can be imported from a shared config
2 | export const locales = [
3 | 'ar',
4 | 'en',
5 | 'en-GB',
6 | 'es',
7 | 'ewe-BJ',
8 | 'fon-BJ',
9 | 'fr',
10 | 'hau-NG',
11 | 'yor-NG',
12 | 'ja',
13 | 'ko',
14 | 'vi',
15 | 'zh-CN',
16 | 'zh-TW'
17 | ] as const;
18 |
19 | export const defaultLocale = 'en';
--------------------------------------------------------------------------------
/i18n/request.ts:
--------------------------------------------------------------------------------
1 | import { notFound } from 'next/navigation';
2 | import { getRequestConfig } from 'next-intl/server';
3 | import { locales } from './i18n';
4 |
5 | export default getRequestConfig(async ({ locale }) => {
6 | // Validate that the incoming `locale` parameter is valid
7 | if (!locales.includes(locale as any)) notFound();
8 |
9 | return {
10 | messages: (await import(`../messages/${locale}.json`)).default,
11 | };
12 | });
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * For a detailed explanation regarding each configuration property, visit:
3 | * https://jestjs.io/docs/configuration
4 | */
5 |
6 | import type {Config} from 'jest';
7 |
8 | const config: Config = {
9 | // All imported modules in your tests should be mocked automatically
10 | // automock: false,
11 |
12 | // Stop running tests after `n` failures
13 | // bail: 0,
14 |
15 | // The directory where Jest should store its cached dependency information
16 | // cacheDirectory: "C:\\Users\\swoos\\AppData\\Local\\Temp\\jest",
17 |
18 | // Automatically clear mock calls, instances, contexts and results before every test
19 | clearMocks: true,
20 |
21 | // Indicates whether the coverage information should be collected while executing the test
22 | collectCoverage: true,
23 |
24 | // An array of glob patterns indicating a set of files for which coverage information should be collected
25 | // collectCoverageFrom: undefined,
26 |
27 | // The directory where Jest should output its coverage files
28 | coverageDirectory: "coverage",
29 |
30 | // An array of regexp pattern strings used to skip coverage collection
31 | // coveragePathIgnorePatterns: [
32 | // "\\\\node_modules\\\\"
33 | // ],
34 |
35 | // Indicates which provider should be used to instrument code for coverage
36 | // coverageProvider: "babel",
37 |
38 | // A list of reporter names that Jest uses when writing coverage reports
39 | // coverageReporters: [
40 | // "json",
41 | // "text",
42 | // "lcov",
43 | // "clover"
44 | // ],
45 |
46 | // An object that configures minimum threshold enforcement for coverage results
47 | // coverageThreshold: undefined,
48 |
49 | // A path to a custom dependency extractor
50 | // dependencyExtractor: undefined,
51 |
52 | // Make calling deprecated APIs throw helpful error messages
53 | // errorOnDeprecated: false,
54 |
55 | // The default configuration for fake timers
56 | // fakeTimers: {
57 | // "enableGlobally": false
58 | // },
59 |
60 | // Force coverage collection from ignored files using an array of glob patterns
61 | // forceCoverageMatch: [],
62 |
63 | // A path to a module which exports an async function that is triggered once before all test suites
64 | // globalSetup: undefined,
65 |
66 | // A path to a module which exports an async function that is triggered once after all test suites
67 | // globalTeardown: undefined,
68 |
69 | // A set of global variables that need to be available in all test environments
70 | // globals: {},
71 |
72 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
73 | // maxWorkers: "50%",
74 |
75 | // An array of directory names to be searched recursively up from the requiring module's location
76 | // moduleDirectories: [
77 | // "node_modules"
78 | // ],
79 |
80 | // An array of file extensions your modules use
81 | // moduleFileExtensions: [
82 | // "js",
83 | // "mjs",
84 | // "cjs",
85 | // "jsx",
86 | // "ts",
87 | // "tsx",
88 | // "json",
89 | // "node"
90 | // ],
91 |
92 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
93 | // moduleNameMapper: {},
94 |
95 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
96 | // modulePathIgnorePatterns: [],
97 |
98 | // Activates notifications for test results
99 | // notify: false,
100 |
101 | // An enum that specifies notification mode. Requires { notify: true }
102 | // notifyMode: "failure-change",
103 |
104 | // A preset that is used as a base for Jest's configuration
105 | preset: 'ts-jest',
106 |
107 | // Run tests from one or more projects
108 | // projects: undefined,
109 |
110 | // Use this configuration option to add custom reporters to Jest
111 | // reporters: undefined,
112 |
113 | // Automatically reset mock state before every test
114 | // resetMocks: false,
115 |
116 | // Reset the module registry before running each individual test
117 | // resetModules: false,
118 |
119 | // A path to a custom resolver
120 | // resolver: undefined,
121 |
122 | // Automatically restore mock state and implementation before every test
123 | // restoreMocks: false,
124 |
125 | // The root directory that Jest should scan for tests and modules within
126 | // rootDir: undefined,
127 |
128 | // A list of paths to directories that Jest should use to search for files in
129 | // roots: [
130 | // ""
131 | // ],
132 |
133 | // Allows you to use a custom runner instead of Jest's default test runner
134 | // runner: "jest-runner",
135 |
136 | // The paths to modules that run some code to configure or set up the testing environment before each test
137 | // setupFiles: [],
138 |
139 | // A list of paths to modules that run some code to configure or set up the testing framework before each test
140 | // setupFilesAfterEnv: [],
141 |
142 | // The number of seconds after which a test is considered as slow and reported as such in the results.
143 | // slowTestThreshold: 5,
144 |
145 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing
146 | // snapshotSerializers: [],
147 |
148 | // The test environment that will be used for testing
149 | testEnvironment: "jsdom",
150 |
151 | // Options that will be passed to the testEnvironment
152 | // testEnvironmentOptions: {},
153 |
154 | // Adds a location field to test results
155 | // testLocationInResults: false,
156 |
157 | // The glob patterns Jest uses to detect test files
158 | // testMatch: [
159 | // "**/__tests__/**/*.[jt]s?(x)",
160 | // "**/?(*.)+(spec|test).[tj]s?(x)"
161 | // ],
162 |
163 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
164 | // testPathIgnorePatterns: [
165 | // "\\\\node_modules\\\\"
166 | // ],
167 |
168 | // The regexp pattern or array of patterns that Jest uses to detect test files
169 | // testRegex: [],
170 |
171 | // This option allows the use of a custom results processor
172 | // testResultsProcessor: undefined,
173 |
174 | // This option allows use of a custom test runner
175 | // testRunner: "jest-circus/runner",
176 |
177 | // A map from regular expressions to paths to transformers
178 | // transform: undefined,
179 |
180 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
181 | // transformIgnorePatterns: [
182 | // "\\\\node_modules\\\\",
183 | // "\\.pnp\\.[^\\\\]+$"
184 | // ],
185 |
186 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
187 | // unmockedModulePathPatterns: undefined,
188 |
189 | // Indicates whether each individual test should be reported during the run
190 | verbose: true,
191 |
192 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
193 | // watchPathIgnorePatterns: [],
194 |
195 | // Whether to use watchman for file crawling
196 | // watchman: true,
197 | };
198 |
199 | export default config;
200 |
--------------------------------------------------------------------------------
/logger.config.mjs:
--------------------------------------------------------------------------------
1 | import log from 'loglevel';
2 | import { logToSentry } from './sentry.client.config.mjs';
3 |
4 | export const configureLogger = () => {
5 | if (process.env.NODE_ENV === 'production') {
6 | // In production, we want to log only to Sentry
7 | log.setLevel('silent');
8 |
9 | log.error = (...args) => {
10 | logToSentry(args.join(' '));
11 | };
12 |
13 | } else if (process.env.NODE_ENV === 'development') {
14 | // In development, we want to log only to the console
15 | log.setLevel('info');
16 | }
17 | };
18 |
19 | // Initialize the logger configuration
20 | configureLogger();
21 |
22 | export default log;
23 |
--------------------------------------------------------------------------------
/messages/zh-CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "HOME": {
3 | "ADD_SELLER": "出售",
4 | "SEARCH_CENTER_DEFAULT_MESSAGE": "您的搜索中心当前设置为默认坐标 [0, 0]。为提升您的体验,请更新您的搜索中心位置。",
5 | "SEARCH_BAR_PLACEHOLDER": "搜索卖家或商品",
6 | "LOCATION_SERVICES": {
7 | "ENABLE_LOCATION_SERVICES_MESSAGE": "要使用此功能,请在设备设置中启用位置服务。",
8 | "DISABLED_LOCATION_SERVICES_MESSAGE": "位置服务已禁用。请启用设备的定位设置。"
9 | },
10 | "AUTHENTICATION": {
11 | "SUCCESSFUL_LOGIN_MESSAGE": "登录成功",
12 | "UNSUCCESSFUL_LOGIN_MESSAGE": "登录失败",
13 | "PI_INFORMATION_NOT_FOUND_MESSAGE": "未找到 Pi 信息"
14 | }
15 | },
16 | "SIDE_NAVIGATION": {
17 | "USER_PREFERENCES_HEADER": "用户偏好",
18 | "PERSONALIZATION_SUBHEADER": "个性化设置",
19 | "LANGUAGES": "语言",
20 | "ABOUT": {
21 | "ABOUT_MAP_OF_PI": "关于 Map of Pi",
22 | "APP_VERSION": "应用程序版本",
23 | "PRIVACY_POLICY": "隐私政策",
24 | "TERMS_OF_SERVICE": "服务条款"
25 | },
26 | "FIND_ME_PREFERENCE_LABEL": "查找我偏好",
27 | "FIND_ME_OPTIONS": {
28 | "PREFERRED_AUTO": "自动",
29 | "PREFERRED_DEVICE_GPS": "使用我设备的 GPS",
30 | "PREFERRED_SEARCH_CENTER": "使用搜索中心"
31 | },
32 | "SEARCH_FILTERS_SUBHEADER": "搜索筛选器",
33 | "SEARCH_FILTERS": {
34 | "INCLUDE_ACTIVE_SELLERS": "包含活跃卖家",
35 | "INCLUDE_INACTIVE_SELLERS": "包含非活跃卖家",
36 | "INCLUDE_TEST_SELLERS": "包含测试卖家"
37 | },
38 | "CONTACT_MAP_OF_PI": "联络 Map of Pi",
39 | "VALIDATION": {
40 | "SUCCESSFUL_PREFERENCES_SUBMISSION": "偏好设置已成功保存",
41 | "UNSUCCESSFUL_PREFERENCES_SUBMISSION": "保存偏好设置时出错"
42 | }
43 | },
44 | "SCREEN": {
45 | "SELLER_REGISTRATION": {
46 | "SELLER_REGISTRATION_HEADER": "卖家注册",
47 | "SELLER_DETAILS_LABEL": "卖家详情",
48 | "EMAIL_LABEL": "电子邮件地址",
49 | "PHONE_NUMBER_LABEL": "电话号码",
50 | "CONTACT_PUBLIC_NOTE": "公开显示电子邮件地址和电话号码。",
51 | "SELLER_DETAILS_PLACEHOLDER": "我通过 Pi 支付进行销售。",
52 | "REVIEWS_SUMMARY_LABEL": "评论概述",
53 | "REVIEWS_SCORE_LABEL": "评论评分",
54 | "SELLER_NAME": "卖家姓名",
55 | "SELLER_TYPE": {
56 | "SELLER_TYPE_LABEL": "卖家类型",
57 | "SELLER_TYPE_OPTIONS": {
58 | "ACTIVE_SELLER": "活跃卖家",
59 | "INACTIVE_SELLER": "非活跃卖家",
60 | "TEST_SELLER": "测试卖家",
61 | "RESTRICTED_SELLER": "受限卖家"
62 | }
63 | },
64 | "SELLER_ADVANCED_SETTINGS_LABEL": "高级卖家设置",
65 | "SELLER_RETAIL_OUTLET_NAME": "零售店名",
66 | "SELLER_ADDRESS_LOCATION_LABEL": "地址或销售地点",
67 | "SELLER_ADDRESS_LOCATION_PLACEHOLDER": "我的地图标记显示我的销售位置",
68 | "SELLER_SETTINGS_LABEL": "卖家设置",
69 | "SELLER_SELL_CENTER": "设定销售中心",
70 | "SELLER_ONLINE_SHOPPING_LABEL": "在线购物",
71 | "MAPPI_ALLOWANCE_LABEL": "剩余的 Mappi 额度",
72 | "FULFILLMENT_METHOD_TYPE": {
73 | "FULFILLMENT_METHOD_TYPE_LABEL": "履行方式",
74 | "FULFILLMENT_METHOD_TYPE_OPTIONS": {
75 | "COLLECTION_BY_BUYER": "买家自取",
76 | "DELIVERED_TO_BUYER": "送货到买家"
77 | }
78 | },
79 | "FULFILLMENT_INSTRUCTIONS_LABEL": "给买家的履行说明",
80 | "FULFILLMENT_INSTRUCTIONS_PLACEHOLDER": "取货地址为卖家的地址。如果选择配送,请输入买家的地址。",
81 | "VALIDATION": {
82 | "EMAIL_VALIDATION": "请输入有效的电子邮件地址",
83 | "SUCCESSFUL_REGISTRATION_SUBMISSION": "注册成功",
84 | "FAILED_REGISTRATION_SUBMISSION": "保存注册信息时出错",
85 | "REGISTRATION_FAILED_USER_NOT_AUTHENTICATED": "注册失败,因为用户未验证身份",
86 | "UNINITIALIZED_SELL_CENTER": "请设定您的销售中心",
87 | "SUCCESSFUL_SELLER_ITEM_SAVED": "卖家商品已成功保存",
88 | "SUCCESSFUL_SELLER_ITEM_DELETED": "卖家商品已成功删除",
89 | "FAILED_SELLER_ITEM_SAVE": "保存卖家商品时出错",
90 | "FAILED_SELLER_ITEM_DELETE": "删除卖家商品时出错",
91 | "SELLER_ITEM_NOT_FOUND": "未找到卖家商品",
92 | "SUCCESSFUL_SAVE_MAPPI_ALLOWANCE_SUFFICIENT": "保存成功。使用的 Mappi 配额为 {mappi_count}",
93 | "FAILED_SAVE_MAPPI_ALLOWANCE_INSUFFICIENT": "保存失败。可用的 Mappi 配额不足"
94 | },
95 | "SELLER_ITEMS_FEATURE": {
96 | "ITEM_LABEL": "商品",
97 | "PRICE_LABEL": "价格",
98 | "STOCK_LABEL": "库存水平",
99 | "DESCRIPTION_LABEL": "描述",
100 | "PHOTO": "照片",
101 | "SELLING_DURATION_LABEL": "销售周期(以周为单位)",
102 | "SELLING_STATUS_OPTIONS": {
103 | "ACTIVE": "活动中",
104 | "EXPIRED": "已过期"
105 | },
106 | "STOCK_LEVEL_OPTIONS": {
107 | "AVAILABLE_1": "1 个可用",
108 | "AVAILABLE_2": "2 个可用",
109 | "AVAILABLE_3": "3 个可用",
110 | "MANY": "多个可用",
111 | "MADE_TO_ORDER": "定制",
112 | "ONGOING_SERVICE": "正在进行的服务",
113 | "SOLD": "已售出"
114 | },
115 | "SELLING_EXPIRATION_DATE": "{expired_by_date} 前销售"
116 | }
117 | },
118 | "BUY_FROM_SELLER": {
119 | "BUY_FROM_SELLER_HEADER": "从卖家购买",
120 | "SELLER_DETAILS_LABEL": "卖家详情",
121 | "SELLER_ADDRESS_POSITION_LABEL": "卖家地址或位置",
122 | "LEAVE_A_REVIEW_MESSAGE": "留下评论",
123 | "FACE_SELECTION_REVIEW_MESSAGE": "选择表情以表示您对该卖家的感受",
124 | "ADDITIONAL_COMMENTS_PLACEHOLDER": "在此输入附加评论",
125 | "FEEDBACK_PHOTO_UPLOAD_LABEL": "上传反馈照片",
126 | "REVIEWS_SUMMARY_LABEL": "评论概述",
127 | "REVIEWS_SCORE_MESSAGE": "评论评分:{seller_review_rating} 满分 5.0",
128 | "SELLER_CONTACT_DETAILS_LABEL": "卖家联系信息",
129 | "SELLER_USERNAME_LABEL": "用户名",
130 | "SELLER_PIONEER_ID_LABEL": "先锋 ID",
131 | "SELLER_PHONE_NUMBER_LABEL": "电话号码",
132 | "SELLER_EMAIL_ADDRESS_LABEL": "电子邮件地址",
133 | "ONLINE_SHOPPING": {
134 | "SELLER_ITEMS_FEATURE": {
135 | "ITEM_LABEL": "商品",
136 | "PRICE_LABEL": "价格",
137 | "DESCRIPTION_LABEL": "描述",
138 | "PHOTO": "照片",
139 | "BUYING_QUANTITY_LABEL": "数量",
140 | "PICK_LABEL": "添加",
141 | "UNPICK_LABEL": "移除"
142 | }
143 | }
144 | },
145 | "REVIEWS": {
146 | "REVIEWS_HEADER": "评论",
147 | "GIVE_REVIEW_SECTION_HEADER": "给这位先锋的评论",
148 | "REVIEWS_GIVEN_SECTION_HEADER": "该先锋给出的评论",
149 | "REVIEWS_RECEIVED_SECTION_HEADER": "该先锋收到的评论",
150 | "VALIDATION": {
151 | "NO_REVIEWS_FOUND": "未找到先锋 {search_value} 的评论",
152 | "NO_PIONEER_FOUND": "未找到先锋 {search_value}"
153 | }
154 | },
155 | "CHECK_REVIEWS_FEEDBACK": {
156 | "CHECK_REVIEWS_NO_FEEDBACK_HEADER": "没有 {seller_id} 的评论",
157 | "CHECK_REVIEWS_FEEDBACK_HEADER": "{seller_id} 的评论列表",
158 | "BY_REVIEWER": "由 {buyer_id}"
159 | },
160 | "REPLY_TO_REVIEW": {
161 | "REPLY_TO_REVIEW_STATIC_HEADER": "回复评论",
162 | "REPLY_TO_REVIEW_SUBHEADER": "您正在回复的评论",
163 | "GIVE_REPLY_TO_REVIEW_SUBHEADER": "给评论的回复",
164 | "REPLY_TO_REVIEW_HEADER": "回复给 {seller_id} 的评论",
165 | "BY_REVIEWER": "由 {buyer_id}",
166 | "REPLY_TO_REVIEW_MESSAGE": "留下您对上述评论的回复",
167 | "FACE_SELECTION_REVIEW_MESSAGE": "选择表情以表示您对与该先锋的互动感受",
168 | "ADDITIONAL_COMMENTS_PLACEHOLDER": "在此输入附加评论",
169 | "FEEDBACK_PHOTO_UPLOAD_LABEL": "上传反馈照片",
170 | "VALIDATION": {
171 | "LOADING_REVIEW_FAILURE": "加载评论时出错",
172 | "SELF_REVIEW_NOT_POSSIBLE": "无法对自己进行评论"
173 | }
174 | }
175 | },
176 | "POPUP": {
177 | "MAP_MARKER": {
178 | "SELLER_SALE_ITEMS_FIELD": "卖家销售的商品",
179 | "DISTANCE_MESSAGE": "距离:XXX 公里"
180 | },
181 | "APP_VERSION_INFO": {
182 | "REPORTING_MESSAGE": "请报告问题至"
183 | },
184 | "PRIVACY_POLICY_INFO": {
185 | "TITLE": "隐私政策",
186 | "LAST_UPDATED": "最后更新",
187 | "EMAIL_ADDRESS": "电子邮件地址",
188 | "SECTIONS": {
189 | "HEADER_1": "简介",
190 | "CONTENT_1": "欢迎使用 Map of Pi 应用程序,由 Map of Pi 团队与 Pi 社区合作开发。本隐私政策概述了您使用我们的移动应用程序时,我们如何收集、使用、披露和保护您的个人信息。",
191 | "HEADER_2": "我们收集的信息",
192 | "SUBHEADER_2_1": "用户信息",
193 | "CONTENT_2_1_1": "当您注册为用户时,我们可能会收集您的姓名、电子邮件地址、用户名和其他相关信息。",
194 | "CONTENT_2_1_2": "我们可能会收集关于您设备的信息,包括设备类型、操作系统和唯一设备标识符。",
195 | "SUBHEADER_2_2": "卖家信息",
196 | "CONTENT_2_2_1": "在平台上注册的卖家可能需要提供卖家信息,包括卖家名称、位置和联系方式。",
197 | "CONTENT_2_2_2": "卖家可以是企业或私人个体。",
198 | "HEADER_3": "我们如何使用您的信息",
199 | "SUBHEADER_3_1": "提供服务",
200 | "CONTENT_3_1_1": "我们使用您的信息来提供和增强 Map of Pi 应用程序的服务,包括用户和卖家功能。",
201 | "SUBHEADER_3_2": "通信",
202 | "CONTENT_3_2_1": "我们可能会使用您的联系方式向您发送与 Map of Pi 应用程序相关的更新、通知和促销信息。",
203 | "SUBHEADER_3_3": "数据分析",
204 | "CONTENT_3_3_1": "我们收集和分析数据以改进应用程序的性能、功能和特点。",
205 | "HEADER_4": "共享您的信息",
206 | "SUBHEADER_4_1": "与卖家共享",
207 | "CONTENT_4_1_1": "用户信息可能会与卖家共享,以便完成订单和进行沟通。",
208 | "SUBHEADER_4_2": "与第三方共享",
209 | "CONTENT_4_2_1": "我们可能会与第三方服务提供商共享信息,以实现应用功能、数据分析和支付处理。",
210 | "HEADER_5": "安全措施",
211 | "CONTENT_5_1": "我们采用行业标准的安全措施来保护您的信息。然而,互联网传输或电子存储方法并非绝对安全。",
212 | "HEADER_6": "您的选择",
213 | "CONTENT_6_1": "您可以通过应用程序设置更新您的账户信息。您可以通过删除 Pi 网络账户来删除您的 Map of Pi 账户,并在下次访问 Map of Pi 时选择删除您的 Map of Pi 数据。您可以选择不接收促销信息。",
214 | "HEADER_7": "隐私政策的变更",
215 | "CONTENT_7_1": "我们可能会根据需要更新本隐私政策。最新版本将发布在我们的网站或应用程序内。",
216 | "HEADER_8": "联系我们",
217 | "CONTENT_8_1": "如果您对本隐私政策有任何问题或疑虑,请通过发送电子邮件联系我们"
218 | }
219 | },
220 | "TERMS_OF_SERVICE_INFO": {
221 | "TITLE": "服务条款",
222 | "LAST_UPDATED": "最后更新",
223 | "EMAIL_ADDRESS": "电子邮件地址",
224 | "SECTIONS": {
225 | "HEADER_1": "接受条款",
226 | "CONTENT_1_1": "欢迎使用由 Map of Pi 团队与 Pi 社区合作开发的 Map of Pi 应用程序。使用本应用程序即表示您同意遵守这些服务条款以及我们的隐私政策。",
227 | "HEADER_2": "应用程序的使用",
228 | "CONTENT_2_1": "您同意按照所有适用的法律法规使用 Map of Pi 应用程序。",
229 | "CONTENT_2_2": "您不得将应用程序用于任何非法或被禁止的目的,也不得以任何可能损害、禁用、过载或削弱应用程序的方式使用该应用程序。",
230 | "CONTENT_2_3": "使用 Map of Pi 需先注册为 Pi 网络的先锋用户。",
231 | "HEADER_3": "知识产权",
232 | "CONTENT_3_1": "Map of Pi 应用程序及其所有内容、特性和功能均归 Map of Pi 所有,并受版权、商标和其他知识产权法律的保护。",
233 | "HEADER_4": "责任限制",
234 | "CONTENT_4_1": "Map of Pi 不对因使用 Map of Pi 应用程序而产生的任何直接、间接、附带、特殊或后果性损害承担责任。",
235 | "HEADER_5": "赔偿",
236 | "CONTENT_5_1": "您同意赔偿并使 Map of Pi 及其附属公司、管理人员、董事、员工和代理人免受任何因您使用 Map of Pi 应用程序而产生的所有索赔、责任、损害、损失或费用(包括律师费)的影响。",
237 | "HEADER_6": "法律适用",
238 | "CONTENT_6_1": "本服务条款应依照英格兰和威尔士的法律解释和执行,不考虑其法律冲突条款,包括因其主题或形成(包括非合同纠纷或索赔)产生的任何争议或索赔。",
239 | "HEADER_7": "条款变更",
240 | "CONTENT_7_1": "我们保留随时更新或修改这些服务条款的权利,恕不另行通知。最新版本将发布在我们的网站或应用程序内。"
241 | }
242 | }
243 | },
244 | "SHARED": {
245 | "PIONEER_LABEL": "先锋",
246 | "BUY": "购买",
247 | "NAVIGATE": "导航",
248 | "NO_COMMENT": "无评论",
249 | "SEARCH_CENTER": "设置搜索中心",
250 | "CHECK_REVIEWS": "查看评论",
251 | "SEARCH_REVIEWS": "搜索评论",
252 | "SEARCH_LOADING": "搜索加载中...",
253 | "REPLY": "回复",
254 | "ADD_ITEM": "添加项目",
255 | "DELETE": "删除",
256 | "CONFIRM_DELETE": "您确定要删除此项目吗?此操作无法撤销。",
257 | "SAVE": "保存",
258 | "CONFIRM": "确认",
259 | "CONFIRM_DIALOG": "您有未保存的更改。确定要离开吗?",
260 | "USER_INFORMATION": {
261 | "PI_USERNAME_LABEL": "Pi 用户名",
262 | "NAME_LABEL": "姓名",
263 | "PHONE_NUMBER_LABEL": "电话号码",
264 | "EMAIL_LABEL": "电子邮件"
265 | },
266 | "PHOTO": {
267 | "UPLOAD_PHOTO_LABEL": "上传照片",
268 | "UPLOAD_PHOTO_PLACEHOLDER": "上传图片以吸引买家(PNG、JPG、JPEG、WEBP)",
269 | "UPLOAD_PHOTO_REVIEW_PLACEHOLDER": "上传图片以支持您的评论(PNG、JPG、JPEG、WEBP)",
270 | "IMAGE_DROP_UPLOAD_MESSAGE": "将图片拖放到此处或浏览",
271 | "SUPPORTS_FILE_MESSAGE": "支持文件格式:PNG、JPG、JPEG、WEBP",
272 | "MISC_LABELS": {
273 | "USER_PREFERENCES_LABEL": "个人资料图片",
274 | "SELLER_IMAGE_LABEL": "卖家图片",
275 | "REVIEW_FEEDBACK_IMAGE_LABEL": "评论图片"
276 | }
277 | },
278 | "REACTION_RATING": {
279 | "UNSAFE": "不安全",
280 | "TRUSTWORTHY": "可信",
281 | "EMOTIONS": {
282 | "DESPAIR": "绝望",
283 | "SAD": "悲伤",
284 | "OKAY": "一般",
285 | "HAPPY": "高兴",
286 | "DELIGHT": "欣喜"
287 | },
288 | "VALIDATION": {
289 | "SUCCESSFUL_REVIEW_SUBMISSION": "评论提交成功",
290 | "UNSUCCESSFUL_REVIEW_SUBMISSION": "评论提交失败",
291 | "SELECT_EMOJI_EXPRESSION": "请选择表情符号"
292 | }
293 | },
294 | "MAP_CENTER": {
295 | "SEARCH_BAR_PLACEHOLDER": "搜索位置、城市或地址",
296 | "VALIDATION": {
297 | "MAP_CENTER_SUCCESS_MESSAGE": "中心点保存成功",
298 | "SELL_CENTER_SANCTIONED_MESSAGE": "您选择的位置似乎位于受限区域。如果确认,您的销售标记可能会从地图中移除。"
299 | }
300 | },
301 | "LOADING_SCREEN_MESSAGE": "加载中...",
302 | "SAVING_SCREEN_MESSAGE": "保存中...",
303 | "VALIDATION": {
304 | "SUBMISSION_FAILED_USER_NOT_AUTHENTICATED": "提交失败,用户未认证",
305 | "UNEXPECTED_ERROR_MESSAGE": "发生意外错误"
306 | }
307 | },
308 | "ERROR": {
309 | "PAGE_NOT_FOUND_HEADER": "页面未找到",
310 | "PAGE_NOT_FOUND_MESSAGE": "抱歉,您查找的页面不存在。"
311 | }
312 | }
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | import createNextIntlPlugin from 'next-intl/plugin';
2 | import { withSentryConfig } from "@sentry/nextjs";
3 |
4 | const withNextIntl = createNextIntlPlugin('./i18n/request.ts');
5 |
6 | /** @type {import('next').NextConfig} */
7 | const nextConfig = {
8 | eslint: {
9 | ignoreDuringBuilds: true,
10 | },
11 | images: {
12 | remotePatterns: [
13 | {
14 | protocol: 'https',
15 | hostname: 'example.com',
16 | port: '',
17 | pathname: '/**',
18 | },
19 | {
20 | protocol: 'http',
21 | hostname: 'localhost',
22 | port: '8001',
23 | pathname: '/**',
24 | },
25 | {
26 | protocol: 'https',
27 | hostname: 'tse3.mm.bing.net',
28 | port: '',
29 | pathname: '/**',
30 | },
31 | {
32 | protocol: 'https',
33 | hostname: 'res.cloudinary.com',
34 | port: '',
35 | pathname: '/**',
36 | }
37 | ],
38 | },
39 | async rewrites() {
40 | return [
41 | {
42 | source: '/api/v1/:path*',
43 | destination: 'http://localhost:8001/api/v1/:path*',
44 | },
45 | ];
46 | },
47 | };
48 |
49 | const sentryWebpackPluginOptions = {
50 | silent: true, // suppress Sentry errors during the build process
51 | };
52 |
53 | // wrap existing configuration with Sentry
54 | const configWithSentry = withSentryConfig(nextConfig, sentryWebpackPluginOptions);
55 |
56 | export default withNextIntl(configWithSentry);
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "map-of-pi-frontend-react",
3 | "version": "1.0.0",
4 | "author": "Map of Pi Team",
5 | "description": "Map of Pi Frontend React",
6 | "license": "PiOS",
7 | "private": true,
8 | "scripts": {
9 | "dev": "next dev -p 4200",
10 | "build": "next build",
11 | "start": "next start -p 4200",
12 | "lint": "next lint",
13 | "test": "jest"
14 | },
15 | "dependencies": {
16 | "@emotion/core": "^10.1.1",
17 | "@emotion/react": "^11.11.4",
18 | "@emotion/styled": "^11.11.5",
19 | "@mui/icons-material": "^5.15.16",
20 | "@mui/material": "^5.15.16",
21 | "@pinetwork-js/sdk": "^0.7.0",
22 | "@sentry/browser": "^8.26.0",
23 | "@sentry/nextjs": "^8.26.0",
24 | "@sentry/node": "^8.27.0",
25 | "axios": "^1.7.5",
26 | "clsx": "^2.1.1",
27 | "cookies-next": "^4.1.1",
28 | "date-fns": "^3.6.0",
29 | "date-fns-tz": "^3.1.3",
30 | "i18next": "^23.11.3",
31 | "js-cookie": "^3.0.5",
32 | "leaflet": "^1.9.4",
33 | "leaflet-control-geocoder": "^2.4.0",
34 | "leaflet-geosearch": "^3.11.1",
35 | "loglevel": "^1.9.1",
36 | "next": "^14.2.28",
37 | "next-intl": "^3.25.3",
38 | "next-logger": "^4.0.0",
39 | "next-themes": "^0.3.0",
40 | "ngx-logger": "^5.0.12",
41 | "react": "^18",
42 | "react-dom": "^18",
43 | "react-i18next": "^14.1.1",
44 | "react-icons": "^5.1.0",
45 | "react-intl": "^6.6.5",
46 | "react-leaflet": "^4.2.1",
47 | "react-logger": "^1.1.0",
48 | "react-phone-number-input": "^3.4.3",
49 | "react-switch": "^7.0.0",
50 | "react-toastify": "^10.0.5",
51 | "sass": "^1.77.0",
52 | "sharp": "^0.33.3",
53 | "zod": "^3.23.8"
54 | },
55 | "devDependencies": {
56 | "@pinetwork-js/api-typing": "^0.7.0",
57 | "@testing-library/jest-dom": "^6.4.2",
58 | "@testing-library/react": "^15.0.2",
59 | "@types/js-cookie": "^3.0.6",
60 | "@types/leaflet": "^1.9.12",
61 | "@types/lodash": "^4.17.5",
62 | "@types/node": "^20",
63 | "@types/react": "^18",
64 | "@types/react-dom": "^18",
65 | "@types/react-leaflet": "^3.0.0",
66 | "@typescript-eslint/eslint-plugin": "^7.7.1",
67 | "@typescript-eslint/parser": "^7.7.1",
68 | "eslint": "^8",
69 | "eslint-config-next": "14.2.2",
70 | "eslint-config-prettier": "^9.1.0",
71 | "eslint-plugin-prettier": "^5.1.3",
72 | "jest": "^29.7.0",
73 | "jest-environment-jsdom": "^29.7.0",
74 | "postcss": "^8",
75 | "prettier": "^3.2.5",
76 | "tailwindcss": "^3.4.1",
77 | "ts-jest": "^29.1.2",
78 | "ts-node": "^10.9.2",
79 | "typescript": "^5"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/default.png
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/business/add-item-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/business/add-item-button.png
--------------------------------------------------------------------------------
/public/images/business/product.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/business/product.png
--------------------------------------------------------------------------------
/public/images/business/upload.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/business/upload.jpg
--------------------------------------------------------------------------------
/public/images/icons/crosshair.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/icons/crosshair.png
--------------------------------------------------------------------------------
/public/images/icons/map-of-pi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/icons/map-of-pi-icon.png
--------------------------------------------------------------------------------
/public/images/icons/map_centers_crosshair.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/icons/map_centers_crosshair.png
--------------------------------------------------------------------------------
/public/images/icons/map_of_pi_logo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/icons/map_of_pi_logo.jpeg
--------------------------------------------------------------------------------
/public/images/icons/scope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/icons/scope.png
--------------------------------------------------------------------------------
/public/images/shared/my_location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/my_location.png
--------------------------------------------------------------------------------
/public/images/shared/review_ratings/trust-o-meter_000.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/review_ratings/trust-o-meter_000.PNG
--------------------------------------------------------------------------------
/public/images/shared/review_ratings/trust-o-meter_050.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/review_ratings/trust-o-meter_050.PNG
--------------------------------------------------------------------------------
/public/images/shared/review_ratings/trust-o-meter_080.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/review_ratings/trust-o-meter_080.PNG
--------------------------------------------------------------------------------
/public/images/shared/review_ratings/trust-o-meter_100.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/review_ratings/trust-o-meter_100.PNG
--------------------------------------------------------------------------------
/public/images/shared/sidebar/close.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/images/shared/sidebar/contact.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/images/shared/sidebar/dark.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/images/shared/sidebar/info.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/images/shared/sidebar/language.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/images/shared/sidebar/light.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/images/shared/sidebar/location.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/images/shared/sidebar/question.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/images/shared/sidebar/settings.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/public/images/shared/sidebar/theme.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/public/images/shared/sidebar/user.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/images/shared/social-media/E-mail_icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/public/images/shared/social-media/discord-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/social-media/discord-icon.png
--------------------------------------------------------------------------------
/public/images/shared/social-media/email-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/social-media/email-icon.png
--------------------------------------------------------------------------------
/public/images/shared/social-media/email-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/public/images/shared/social-media/email-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/social-media/email-logo.png
--------------------------------------------------------------------------------
/public/images/shared/social-media/facebook.icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/social-media/facebook.icon.png
--------------------------------------------------------------------------------
/public/images/shared/social-media/instagram-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/social-media/instagram-icon.png
--------------------------------------------------------------------------------
/public/images/shared/social-media/tiktok-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
28 |
--------------------------------------------------------------------------------
/public/images/shared/social-media/x-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/social-media/x-icon.png
--------------------------------------------------------------------------------
/public/images/shared/social-media/youtube-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/social-media/youtube-icon.png
--------------------------------------------------------------------------------
/public/images/shared/upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/upload.png
--------------------------------------------------------------------------------
/public/images/shared/upload_old.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/map-of-pi/map-of-pi-frontend-react/e5de66f0fe4656a0cd66d1060cf5bebd7df1bbfe/public/images/shared/upload_old.png
--------------------------------------------------------------------------------
/public/validation-key.txt:
--------------------------------------------------------------------------------
1 | 5fc61d0d7eab437117d410f84d49df5116ad216192df5820b15671a6099602beeedb1aa96ab226a03ecbd3abfe125e9cf1ba1b597be2bb71cedef66d5d81d2f8
--------------------------------------------------------------------------------
/sentry.client.config.mjs:
--------------------------------------------------------------------------------
1 | import * as Sentry from "@sentry/nextjs";
2 | import { replayIntegration } from '@sentry/browser';
3 |
4 | // initialize Sentry only in production environment
5 | if (process.env.NODE_ENV === 'production') {
6 | try {
7 | Sentry.init({
8 | dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
9 | tracesSampleRate: 0.1,
10 | replaysSessionSampleRate: 0.1,
11 | replaysOnErrorSampleRate: 1.0
12 | });
13 | } catch (error) {
14 | throw new Error(`Failed connection to Sentry: ${error.message}`);
15 | }
16 | }
17 |
18 | export const logToSentry = (message) => {
19 | Sentry.captureException(new Error(message));
20 | };
--------------------------------------------------------------------------------
/src/app/[locale]/layout.tsx:
--------------------------------------------------------------------------------
1 | import { NextIntlClientProvider, useMessages } from 'next-intl';
2 | import { setRequestLocale } from 'next-intl/server';
3 | import { Lato } from 'next/font/google';
4 | import { locales } from '../../../i18n/i18n';
5 | import { Providers } from '../providers';
6 | import Navbar from '@/components/shared/navbar/Navbar';
7 | import logger from '../../../logger.config.mjs';
8 |
9 | export const dynamic = 'force-dynamic';
10 |
11 | const lato = Lato({ weight: '400', subsets: ['latin'], display: 'swap' });
12 |
13 | export async function generateStaticParams() {
14 | return locales.map((locale) => { locale })
15 | };
16 |
17 | export default function LocaleLayout({
18 | children,
19 | params: { locale },
20 | }: {
21 | children: React.ReactNode;
22 | params: { locale: string };
23 | }) {
24 | // Enable static rendering
25 | setRequestLocale(locale);
26 |
27 | // Receive messages provided in `i18n.ts`
28 | const messages = useMessages();
29 |
30 | // log the locale and messages loading
31 | logger.info(`Rendering LocaleLayout for locale: ${locale}`);
32 | if (messages) {
33 | logger.info('Messages loaded successfully.');
34 | } else {
35 | logger.warn('No messages found for the given locale.');
36 | }
37 |
38 | return (
39 |
40 |
41 |
42 | Map of Pi
43 |
44 |
48 |
52 |
53 |
54 |
58 |
62 |
63 |
64 |
68 |
73 |
79 |
85 |
89 |
93 |
97 |
98 |
99 | {/* Google tag (gtag.js) */}
100 |
101 |
109 |
110 |
112 |
113 |
114 |
115 |